Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(app): add error recovery analytics #15787

Merged
merged 7 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ import {
useErrorRecoveryFlows,
ErrorRecoveryFlows,
} from '../../ErrorRecoveryFlows'
import { useRecoveryAnalytics } from '../../ErrorRecoveryFlows/hooks'

import type { Run, RunError, RunStatus } from '@opentrons/api-client'
import type { IconName } from '@opentrons/components'
Expand Down Expand Up @@ -148,6 +149,7 @@ export function ProtocolRunHeader({
protocolKey,
isProtocolAnalyzing,
} = useProtocolDetailsForRun(runId)
const { reportRecoveredRunResult } = useRecoveryAnalytics()

const { trackProtocolRunEvent } = useTrackProtocolRunEvent(runId, robotName)
const robotAnalyticsData = useRobotAnalyticsData(robotName)
Expand All @@ -161,6 +163,7 @@ export function ProtocolRunHeader({
const { startedAt, stoppedAt, completedAt } = useRunTimestamps(runId)
const [showRunFailedModal, setShowRunFailedModal] = React.useState(false)
const [showDropTipBanner, setShowDropTipBanner] = React.useState(true)
const [enteredER, setEnteredER] = React.useState(false)
const isResetRunLoadingRef = React.useRef(false)
const { data: runRecord } = useNotifyRunQuery(runId, { staleTime: Infinity })
const highestPriorityError =
Expand Down Expand Up @@ -234,6 +237,7 @@ export function ProtocolRunHeader({

// Side effects dependent on the current run state.
React.useEffect(() => {
reportRecoveredRunResult(runStatus, enteredER)
// After a user-initiated stopped run, close the run current run automatically.
if (runStatus === RUN_STATUS_STOPPED && isRunCurrent && runId != null) {
trackProtocolRunEvent({
Expand All @@ -244,6 +248,9 @@ export function ProtocolRunHeader({
})
closeCurrentRun()
}
if (runStatus === RUN_STATUS_AWAITING_RECOVERY) {
setEnteredER(true)
}
}, [runStatus, isRunCurrent, runId, closeCurrentRun])

const startedAtTimestamp =
Expand Down
17 changes: 15 additions & 2 deletions app/src/organisms/ErrorRecoveryFlows/ErrorRecoveryWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { RECOVERY_MAP } from './constants'

import type { RobotType } from '@opentrons/shared-data'
import type { RecoveryContentProps } from './types'
import type { ERUtilsResults } from './hooks'
import type { ERUtilsResults, UseRecoveryAnalyticsResult } from './hooks'
import type { ErrorRecoveryFlowsProps } from '.'

interface UseERWizardResult {
Expand Down Expand Up @@ -59,6 +59,7 @@ export type ErrorRecoveryWizardProps = ErrorRecoveryFlowsProps &
robotType: RobotType
isOnDevice: boolean
isDoorOpen: boolean
analytics: UseRecoveryAnalyticsResult
}

export function ErrorRecoveryWizard(
Expand All @@ -84,11 +85,23 @@ export function ErrorRecoveryWizard(
export function ErrorRecoveryComponent(
props: RecoveryContentProps
): JSX.Element {
const { recoveryMap, hasLaunchedRecovery, isDoorOpen, isOnDevice } = props
const {
recoveryMap,
hasLaunchedRecovery,
isDoorOpen,
isOnDevice,
analytics,
} = props
const { route, step } = recoveryMap
const { t } = useTranslation('error_recovery')
const { showModal, toggleModal } = useErrorDetailsModal()

React.useEffect(() => {
if (showModal) {
analytics.reportViewErrorDetailsEvent(route, step)
}
}, [analytics, route, showModal, step])

const buildTitleHeading = (): JSX.Element => {
const titleText = hasLaunchedRecovery ? t('recovery_mode') : t('cancel_run')
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export function SelectRecoveryOptionHome({
tipStatusUtils,
currentRecoveryOptionUtils,
getRecoveryOptionCopy,
analytics,
...rest
}: RecoveryContentProps): JSX.Element | null {
const { t } = useTranslation('error_recovery')
Expand All @@ -68,6 +69,7 @@ export function SelectRecoveryOptionHome({
<RecoveryODDOneDesktopTwoColumnContentWrapper
footerDetails={{
primaryBtnOnClick: () => {
analytics.reportActionSelectedEvent(selectedRoute)
setSelectedRecoveryOption(selectedRoute)
void proceedToRouteAndStep(selectedRoute as RecoveryRoute)
},
Expand Down
27 changes: 20 additions & 7 deletions app/src/organisms/ErrorRecoveryFlows/RunPausedSplash.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
PrimaryButton,
SecondaryButton,
} from '@opentrons/components'
import { useHost } from '@opentrons/react-api-client'

import { useErrorName } from './hooks'
import { getErrorKind } from './utils'
Expand All @@ -35,8 +36,7 @@ import {

import type { RobotType } from '@opentrons/shared-data'
import type { ErrorRecoveryFlowsProps } from '.'
import type { ERUtilsResults } from './hooks'
import { useHost } from '@opentrons/react-api-client'
import type { ERUtilsResults, UseRecoveryAnalyticsResult } from './hooks'

export function useRunPausedSplash(
isOnDevice: boolean,
Expand All @@ -53,17 +53,25 @@ type RunPausedSplashProps = ERUtilsResults & {
protocolAnalysis: ErrorRecoveryFlowsProps['protocolAnalysis']
robotType: RobotType
toggleERWiz: (launchER: boolean) => Promise<void>
analytics: UseRecoveryAnalyticsResult
}
export function RunPausedSplash(
props: RunPausedSplashProps
): JSX.Element | null {
const { isOnDevice, toggleERWiz, routeUpdateActions, failedCommand } = props
const {
isOnDevice,
toggleERWiz,
routeUpdateActions,
failedCommand,
analytics,
} = props
const { t } = useTranslation('error_recovery')
const errorKind = getErrorKind(failedCommand)
const title = useErrorName(errorKind)
const host = useHost()

const { proceedToRouteAndStep } = routeUpdateActions
const { reportInitialActionEvent } = analytics

const buildTitleHeadingDesktop = (): JSX.Element => {
return (
Expand All @@ -75,12 +83,17 @@ export function RunPausedSplash(

// Do not launch error recovery, but do utilize the wizard's cancel route.
const onCancelClick = (): Promise<void> => {
return toggleERWiz(false).then(() =>
proceedToRouteAndStep(RECOVERY_MAP.CANCEL_RUN.ROUTE)
)
return toggleERWiz(false).then(() => {
reportInitialActionEvent('cancel-run')
void proceedToRouteAndStep(RECOVERY_MAP.CANCEL_RUN.ROUTE)
})
}

const onLaunchERClick = (): Promise<void> => toggleERWiz(true)
const onLaunchERClick = (): Promise<void> => {
return toggleERWiz(true).then(() => {
reportInitialActionEvent('launch-recovery')
})
}

// TODO(jh 05-22-24): The hardcoded Z-indexing is non-ideal but must be done to keep the splash page above
// several components in the RunningProtocol page. Investigate why these components have seemingly arbitrary zIndex values
Expand Down
8 changes: 8 additions & 0 deletions app/src/organisms/ErrorRecoveryFlows/__fixtures__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,12 @@ export const mockRecoveryContentProps: RecoveryContentProps = {
mockRobotSideAnalysis.commands[mockRobotSideAnalysis.commands.length - 1],
],
recoveryActionMutationUtils: {} as any,
analytics: {
reportRecoveredRunResult: () => {},
reportErrorEvent: () => {},
reportViewErrorDetailsEvent: () => {},
reportActionSelectedEvent: () => {},
reportInitialActionEvent: () => {},
reportActionSelectedResult: () => {},
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
useCurrentlyRecoveringFrom,
useERUtils,
useShowDoorInfo,
useRecoveryAnalytics,
} from '../hooks'
import { useFeatureFlag } from '../../../redux/config'
import { useERWizard, ErrorRecoveryWizard } from '../ErrorRecoveryWizard'
Expand Down Expand Up @@ -144,6 +145,9 @@ describe('ErrorRecovery', () => {
vi.mocked(useRunPausedSplash).mockReturnValue(true)
vi.mocked(useERUtils).mockReturnValue({ routeUpdateActions: {} } as any)
vi.mocked(useShowDoorInfo).mockReturnValue(false)
vi.mocked(useRecoveryAnalytics).mockReturnValue({
reportErrorEvent: vi.fn(),
} as any)
})

it('renders the wizard when the wizard is toggled on', () => {
Expand Down
Loading
Loading