diff --git a/app/src/assets/localization/en/error_recovery.json b/app/src/assets/localization/en/error_recovery.json
index 30ff25a4968..99589c63216 100644
--- a/app/src/assets/localization/en/error_recovery.json
+++ b/app/src/assets/localization/en/error_recovery.json
@@ -18,6 +18,7 @@
"continue": "Continue",
"continue_run_now": "Continue run now",
"continue_to_drop_tip": "Continue to drop tip",
+ "do_you_need_to_blowout": "First, do you need to blow out aspirated liquid?",
"door_open_gripper_home": "The robot door must be closed for the gripper to home its Z-axis before you can continue manually moving labware.",
"ensure_lw_is_accurately_placed": "Ensure labware is accurately placed in the slot to prevent further errors.",
"error": "Error",
@@ -49,13 +50,13 @@
"manually_move_lw_on_deck": "Manually move labware on deck",
"manually_replace_lw_and_retry": "Manually replace labware on deck and retry step",
"manually_replace_lw_on_deck": "Manually replace labware on deck",
+ "na": "N/A",
"next_step": "Next step",
"next_try_another_action": "Next, you can try another recovery action or cancel the run.",
"no_liquid_detected": "No liquid detected",
"overpressure_is_usually_caused": "Overpressure is usually caused by a tip contacting labware, a clog, or moving viscous liquid too quickly",
"pick_up_tips": "Pick up tips",
"pipette_overpressure": "Pipette overpressure",
- "do_you_need_to_blowout": "First, do you need to blowout aspirated liquid?",
"proceed_to_cancel": "Proceed to cancel",
"proceed_to_tip_selection": "Proceed to tip selection",
"recovery_action_failed": "{{action}} failed",
@@ -76,6 +77,7 @@
"retry_with_new_tips": "Retry with new tips",
"retry_with_same_tips": "Retry with same tips",
"retrying_step_succeeded": "Retrying step {{step}} succeeded.",
+ "retrying_step_succeeded_na": "Retrying current step succeeded.",
"return_to_menu": "Return to menu",
"robot_door_is_open": "Robot door is open",
"robot_is_canceling_run": "Robot is canceling the run",
@@ -93,6 +95,7 @@
"skip_to_next_step_new_tips": "Skip to next step with new tips",
"skip_to_next_step_same_tips": "Skip to next step with same tips",
"skipping_to_step_succeeded": "Skipping to step {{step}} succeeded.",
+ "skipping_to_step_succeeded_na": "Skipping to next step succeeded.",
"stand_back": "Stand back, robot is in motion",
"stand_back_picking_up_tips": "Stand back, picking up tips",
"stand_back_resuming": "Stand back, resuming current step",
diff --git a/app/src/assets/localization/en/run_details.json b/app/src/assets/localization/en/run_details.json
index 28df0734619..98443901364 100644
--- a/app/src/assets/localization/en/run_details.json
+++ b/app/src/assets/localization/en/run_details.json
@@ -62,6 +62,7 @@
"module_controls": "Module Controls",
"module_slot_number": "Slot {{slot_number}}",
"move_labware": "Move Labware",
+ "na": "N/A",
"name": "Name",
"no_files_included": "No protocol files included",
"no_of_error": "{{count}} error",
@@ -144,6 +145,7 @@
"status_succeeded": "Completed",
"step": "Step",
"step_failed": "Step failed",
+ "step_na": "Step: N/A",
"step_number": "Step {{step_number}}:",
"steps_total": "{{count}} steps total",
"stored_labware_offset_data": "Stored Labware Offset data that applies to this protocol",
diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/RunHeaderModalContainer.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/RunHeaderModalContainer.tsx
index 3be4f607208..0c306339f69 100644
--- a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/RunHeaderModalContainer.tsx
+++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/RunHeaderModalContainer.tsx
@@ -51,7 +51,7 @@ export function RunHeaderModalContainer(
) : null}
diff --git a/app/src/organisms/Desktop/RunProgressMeter/__tests__/RunProgressMeter.test.tsx b/app/src/organisms/Desktop/RunProgressMeter/__tests__/RunProgressMeter.test.tsx
index 4217ce8618a..3618b0ce586 100644
--- a/app/src/organisms/Desktop/RunProgressMeter/__tests__/RunProgressMeter.test.tsx
+++ b/app/src/organisms/Desktop/RunProgressMeter/__tests__/RunProgressMeter.test.tsx
@@ -114,7 +114,7 @@ describe('RunProgressMeter', () => {
it('should show only the total count of commands in run and not show the meter when protocol is non-deterministic', () => {
vi.mocked(useCommandQuery).mockReturnValue({ data: null } as any)
render(props)
- expect(screen.getByText('Current Step ?/?:')).toBeTruthy()
+ expect(screen.getByText('Current Step N/A:')).toBeTruthy()
expect(screen.queryByText('MOCK PROGRESS BAR')).toBeFalsy()
})
it('should give the correct info when run status is idle', () => {
diff --git a/app/src/organisms/Desktop/RunProgressMeter/hooks/useRunProgressCopy.tsx b/app/src/organisms/Desktop/RunProgressMeter/hooks/useRunProgressCopy.tsx
index 8c522b4ff22..65e2f27d6b3 100644
--- a/app/src/organisms/Desktop/RunProgressMeter/hooks/useRunProgressCopy.tsx
+++ b/app/src/organisms/Desktop/RunProgressMeter/hooks/useRunProgressCopy.tsx
@@ -109,7 +109,9 @@ export function useRunProgressCopy({
if (runStatus === RUN_STATUS_IDLE) {
return `${stepType}:`
} else if (isTerminalStatus && currentStepNumber == null) {
- return `${stepType}: N/A`
+ return `${stepType}: ${t('na')}`
+ } else if (hasRunDiverged) {
+ return `${stepType} ${t('na')}:`
} else {
const getCountString = (): string => {
const current = currentStepNumber ?? '?'
diff --git a/app/src/organisms/ErrorRecoveryFlows/ErrorRecoveryWizard.tsx b/app/src/organisms/ErrorRecoveryFlows/ErrorRecoveryWizard.tsx
index e1dd7c5add2..1c62471380d 100644
--- a/app/src/organisms/ErrorRecoveryFlows/ErrorRecoveryWizard.tsx
+++ b/app/src/organisms/ErrorRecoveryFlows/ErrorRecoveryWizard.tsx
@@ -82,7 +82,7 @@ export function ErrorRecoveryWizard(
recoveryCommands,
routeUpdateActions,
} = props
- const errorKind = getErrorKind(failedCommand?.byRunRecord ?? null)
+ const errorKind = getErrorKind(failedCommand)
useInitialPipetteHome({
hasLaunchedRecovery,
diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/ManageTips.test.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/ManageTips.test.tsx
index 39b3ab57256..941b19081c7 100644
--- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/ManageTips.test.tsx
+++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/ManageTips.test.tsx
@@ -282,7 +282,7 @@ describe('useDropTipFlowUtils', () => {
testingRender(result.current.copyOverrides.beforeBeginningTopText as any)
- screen.getByText('First, do you need to blowout aspirated liquid?')
+ screen.getByText('First, do you need to blow out aspirated liquid?')
testingRender(result.current.copyOverrides.tipDropCompleteBtnCopy as any)
diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoverySplash.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoverySplash.tsx
index c9006f5d552..7a17b443508 100644
--- a/app/src/organisms/ErrorRecoveryFlows/RecoverySplash.tsx
+++ b/app/src/organisms/ErrorRecoveryFlows/RecoverySplash.tsx
@@ -85,7 +85,7 @@ export function RecoverySplash(props: RecoverySplashProps): JSX.Element | null {
resumePausedRecovery,
} = props
const { t } = useTranslation('error_recovery')
- const errorKind = getErrorKind(failedCommand?.byRunRecord ?? null)
+ const errorKind = getErrorKind(failedCommand)
const title = useErrorName(errorKind)
const { makeToast } = useToaster()
diff --git a/app/src/organisms/ErrorRecoveryFlows/__fixtures__/index.ts b/app/src/organisms/ErrorRecoveryFlows/__fixtures__/index.ts
index c79e270bbed..1a815b99c1e 100644
--- a/app/src/organisms/ErrorRecoveryFlows/__fixtures__/index.ts
+++ b/app/src/organisms/ErrorRecoveryFlows/__fixtures__/index.ts
@@ -53,7 +53,7 @@ export const mockPickUpTipLabware: LoadedLabware = {
// TODO: jh(08-07-24): update the "byAnalysis" mockFailedCommand.
export const mockRecoveryContentProps: RecoveryContentProps = {
- failedCommandByRunRecord: mockFailedCommand,
+ unvalidatedFailedCommand: mockFailedCommand,
failedCommand: {
byRunRecord: mockFailedCommand,
byAnalysis: mockFailedCommand,
diff --git a/app/src/organisms/ErrorRecoveryFlows/__tests__/ErrorRecoveryFlows.test.tsx b/app/src/organisms/ErrorRecoveryFlows/__tests__/ErrorRecoveryFlows.test.tsx
index d73d402585d..04719afca56 100644
--- a/app/src/organisms/ErrorRecoveryFlows/__tests__/ErrorRecoveryFlows.test.tsx
+++ b/app/src/organisms/ErrorRecoveryFlows/__tests__/ErrorRecoveryFlows.test.tsx
@@ -146,7 +146,7 @@ describe('ErrorRecoveryFlows', () => {
beforeEach(() => {
props = {
runStatus: RUN_STATUS_AWAITING_RECOVERY,
- failedCommandByRunRecord: mockFailedCommand,
+ unvalidatedFailedCommand: mockFailedCommand,
runId: 'MOCK_RUN_ID',
protocolAnalysis: null,
}
diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useFailedLabwareUtils.test.tsx b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useFailedLabwareUtils.test.tsx
index b0716af5c8a..f8559163adb 100644
--- a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useFailedLabwareUtils.test.tsx
+++ b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useFailedLabwareUtils.test.tsx
@@ -92,7 +92,7 @@ describe('getRelevantFailedLabwareCmdFrom', () => {
},
}
const result = getRelevantFailedLabwareCmdFrom({
- failedCommandByRunRecord: failedLiquidProbeCommand,
+ failedCommand: { byRunRecord: failedLiquidProbeCommand } as any,
})
expect(result).toEqual(failedLiquidProbeCommand)
})
@@ -117,11 +117,13 @@ describe('getRelevantFailedLabwareCmdFrom', () => {
overpressureErrorKinds.forEach(([commandType, errorType]) => {
const result = getRelevantFailedLabwareCmdFrom({
- failedCommandByRunRecord: {
- ...failedCommand,
- commandType,
- error: { isDefined: true, errorType },
- },
+ failedCommand: {
+ byRunRecord: {
+ ...failedCommand,
+ commandType,
+ error: { isDefined: true, errorType },
+ },
+ } as any,
runCommands,
})
expect(result).toBe(pickUpTipCommand)
@@ -138,27 +140,33 @@ describe('getRelevantFailedLabwareCmdFrom', () => {
},
}
const result = getRelevantFailedLabwareCmdFrom({
- failedCommandByRunRecord: failedGripperCommand,
+ failedCommand: { byRunRecord: failedGripperCommand } as any,
})
expect(result).toEqual(failedGripperCommand)
})
it('should return null for GENERAL_ERROR error kind', () => {
const result = getRelevantFailedLabwareCmdFrom({
- failedCommandByRunRecord: {
- ...failedCommand,
- error: { errorType: 'literally anything else' },
- },
+ failedCommand: {
+ byRunRecord: {
+ ...failedCommand,
+ error: {
+ errorType: 'literally anything else',
+ },
+ },
+ } as any,
})
expect(result).toBeNull()
})
it('should return null for unhandled error kinds', () => {
const result = getRelevantFailedLabwareCmdFrom({
- failedCommandByRunRecord: {
- ...failedCommand,
- error: { errorType: 'SOME_UNHANDLED_ERROR' },
- },
+ failedCommand: {
+ byRunRecord: {
+ ...failedCommand,
+ error: { errorType: 'SOME_UNHANDLED_ERROR' },
+ },
+ } as any,
})
expect(result).toBeNull()
})
diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryCommands.test.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryCommands.test.ts
index 4079e8a8f1e..565ef49c951 100644
--- a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryCommands.test.ts
+++ b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryCommands.test.ts
@@ -56,7 +56,11 @@ describe('useRecoveryCommands', () => {
const props = {
runId: mockRunId,
- failedCommandByRunRecord: mockFailedCommand,
+ failedCommand: {
+ byRunRecord: mockFailedCommand,
+ byAnalysis: mockFailedCommand,
+ },
+ unvalidatedFailedCommand: mockFailedCommand,
failedLabwareUtils: mockFailedLabwareUtils,
routeUpdateActions: mockRouteUpdateActions,
recoveryToastUtils: { makeSuccessToast: mockMakeSuccessToast } as any,
@@ -144,24 +148,29 @@ describe('useRecoveryCommands', () => {
'prepareToAspirate',
] as const).forEach(inPlaceCommandType => {
it(`Should move to retryLocation if failed command is ${inPlaceCommandType} and error is appropriate when retrying`, async () => {
- const { result } = renderHook(() =>
- useRecoveryCommands({
- runId: mockRunId,
- failedCommandByRunRecord: {
- ...mockFailedCommand,
- commandType: inPlaceCommandType,
- params: {
- pipetteId: 'mock-pipette-id',
- },
- error: {
- errorType: 'overpressure',
- errorCode: '3006',
- isDefined: true,
- errorInfo: {
- retryLocation: [1, 2, 3],
- },
+ const { result } = renderHook(() => {
+ const failedCommand = {
+ ...mockFailedCommand,
+ commandType: inPlaceCommandType,
+ params: {
+ pipetteId: 'mock-pipette-id',
+ },
+ error: {
+ errorType: 'overpressure',
+ errorCode: '3006',
+ isDefined: true,
+ errorInfo: {
+ retryLocation: [1, 2, 3],
},
},
+ }
+ return useRecoveryCommands({
+ runId: mockRunId,
+ failedCommand: {
+ byRunRecord: failedCommand,
+ byAnalysis: failedCommand,
+ },
+ unvalidatedFailedCommand: failedCommand,
failedLabwareUtils: mockFailedLabwareUtils,
routeUpdateActions: mockRouteUpdateActions,
recoveryToastUtils: {} as any,
@@ -171,7 +180,7 @@ describe('useRecoveryCommands', () => {
} as any,
selectedRecoveryOption: RECOVERY_MAP.RETRY_NEW_TIPS.ROUTE,
})
- )
+ })
await act(async () => {
await result.current.retryFailedCommand()
})
@@ -245,7 +254,7 @@ describe('useRecoveryCommands', () => {
const testProps = {
...props,
- failedCommandByRunRecord: mockFailedCmdWithPipetteId,
+ unvalidatedFailedCommand: mockFailedCmdWithPipetteId,
failedLabwareUtils: {
...mockFailedLabwareUtils,
failedLabware: mockFailedLabware,
@@ -312,7 +321,7 @@ describe('useRecoveryCommands', () => {
const testProps = {
...props,
- failedCommandByRunRecord: mockFailedCommandWithError,
+ unvalidatedFailedCommand: mockFailedCommandWithError,
}
const { result, rerender } = renderHook(() =>
@@ -349,7 +358,7 @@ describe('useRecoveryCommands', () => {
const testProps = {
...props,
- failedCommandByRunRecord: mockFailedCommandWithError,
+ unvalidatedFailedCommand: mockFailedCommandWithError,
}
mockUpdateErrorRecoveryPolicy.mockRejectedValueOnce(
diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryToasts.test.tsx b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryToasts.test.tsx
index c572618bbcc..c9874eb5532 100644
--- a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryToasts.test.tsx
+++ b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryToasts.test.tsx
@@ -30,7 +30,11 @@ let mockMakeToast: Mock
const DEFAULT_PROPS: BuildToast = {
isOnDevice: false,
- currentStepCount: 1,
+ stepCounts: {
+ currentStepNumber: 1,
+ hasRunDiverged: false,
+ totalStepCount: 1,
+ },
selectedRecoveryOption: RECOVERY_MAP.RETRY_SAME_TIPS.ROUTE,
commandTextData: { commands: [] } as any,
robotType: FLEX_ROBOT_TYPE,
@@ -187,13 +191,11 @@ describe('getStepNumber', () => {
})
it('should handle a falsy currentStepCount', () => {
- expect(getStepNumber(RECOVERY_MAP.RETRY_SAME_TIPS.ROUTE, null)).toBe('?')
+ expect(getStepNumber(RECOVERY_MAP.RETRY_SAME_TIPS.ROUTE, null)).toBe(null)
})
it('should handle unknown recovery option', () => {
- expect(getStepNumber('UNKNOWN_OPTION' as any, 3)).toBe(
- 'HANDLE RECOVERY TOAST OPTION EXPLICITLY.'
- )
+ expect(getStepNumber('UNKNOWN_OPTION' as any, 3)).toBeNull()
})
})
@@ -234,17 +236,17 @@ describe('useRecoveryFullCommandText', () => {
expect(result.current).toBeNull()
})
- it('should return stepNumber if it is a string', () => {
+ it('should return null if there is no current step count', () => {
const { result } = renderHook(() =>
useRecoveryFullCommandText({
robotType: FLEX_ROBOT_TYPE,
- stepNumber: '?',
+ stepNumber: null,
commandTextData: { commands: [] } as any,
allRunDefs: [],
})
)
- expect(result.current).toBe('?')
+ expect(result.current).toBeNull()
})
it('should truncate TC command', () => {
diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts
index 57691a30e55..b1a55ea12b8 100644
--- a/app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts
+++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts
@@ -87,6 +87,7 @@ export function useERUtils({
runStatus,
showTakeover,
allRunDefs,
+ unvalidatedFailedCommand,
labwareDefinitionsByUri,
}: ERUtilsProps): ERUtilsResults {
const { data: attachedInstruments } = useInstrumentsQuery()
@@ -100,7 +101,6 @@ export function useERUtils({
cursor: 0,
pageLength: 999,
})
- const failedCommandByRunRecord = failedCommand?.byRunRecord ?? null
const stepCounts = useRunningStepCounts(runId, runCommands)
@@ -120,7 +120,7 @@ export function useERUtils({
)
const recoveryToastUtils = useRecoveryToasts({
- currentStepCount: stepCounts.currentStepNumber,
+ stepCounts,
selectedRecoveryOption: currentRecoveryOptionUtils.selectedRecoveryOption,
isOnDevice,
commandTextData: protocolAnalysis,
@@ -152,7 +152,7 @@ export function useERUtils({
})
const failedLabwareUtils = useFailedLabwareUtils({
- failedCommandByRunRecord,
+ failedCommand,
protocolAnalysis,
failedPipetteInfo,
runRecord,
@@ -161,7 +161,8 @@ export function useERUtils({
const recoveryCommands = useRecoveryCommands({
runId,
- failedCommandByRunRecord,
+ failedCommand,
+ unvalidatedFailedCommand,
failedLabwareUtils,
routeUpdateActions,
recoveryToastUtils,
diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useFailedLabwareUtils.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useFailedLabwareUtils.ts
index 72969d884d4..bc077d4c624 100644
--- a/app/src/organisms/ErrorRecoveryFlows/hooks/useFailedLabwareUtils.ts
+++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useFailedLabwareUtils.ts
@@ -30,10 +30,10 @@ import type {
} from '@opentrons/shared-data'
import type { DisplayLocationSlotOnlyParams } from '/app/local-resources/labware'
import type { ErrorRecoveryFlowsProps } from '..'
-import type { ERUtilsProps } from './useERUtils'
+import type { FailedCommandBySource } from './useRetainedFailedCommandBySource'
interface UseFailedLabwareUtilsProps {
- failedCommandByRunRecord: ERUtilsProps['failedCommandByRunRecord']
+ failedCommand: FailedCommandBySource | null
protocolAnalysis: ErrorRecoveryFlowsProps['protocolAnalysis']
failedPipetteInfo: PipetteData | null
runCommands?: CommandsData
@@ -67,16 +67,17 @@ export type UseFailedLabwareUtilsResult = UseTipSelectionUtilsResult & {
* For no liquid detected errors, the relevant labware is the well in which no liquid was detected.
*/
export function useFailedLabwareUtils({
- failedCommandByRunRecord,
+ failedCommand,
protocolAnalysis,
failedPipetteInfo,
runCommands,
runRecord,
}: UseFailedLabwareUtilsProps): UseFailedLabwareUtilsResult {
+ const failedCommandByRunRecord = failedCommand?.byRunRecord ?? null
const recentRelevantFailedLabwareCmd = useMemo(
() =>
getRelevantFailedLabwareCmdFrom({
- failedCommandByRunRecord,
+ failedCommand,
runCommands,
}),
[failedCommandByRunRecord?.key, runCommands?.meta.totalLength]
@@ -129,16 +130,17 @@ type FailedCommandRelevantLabware =
| null
interface RelevantFailedLabwareCmd {
- failedCommandByRunRecord: ErrorRecoveryFlowsProps['failedCommandByRunRecord']
+ failedCommand: FailedCommandBySource | null
runCommands?: CommandsData
}
// Return the actual command that contains the info relating to the relevant labware.
export function getRelevantFailedLabwareCmdFrom({
- failedCommandByRunRecord,
+ failedCommand,
runCommands,
}: RelevantFailedLabwareCmd): FailedCommandRelevantLabware {
- const errorKind = getErrorKind(failedCommandByRunRecord)
+ const failedCommandByRunRecord = failedCommand?.byRunRecord ?? null
+ const errorKind = getErrorKind(failedCommand)
switch (errorKind) {
case ERROR_KINDS.NO_LIQUID_DETECTED:
@@ -161,7 +163,7 @@ export function getRelevantFailedLabwareCmdFrom({
// Returns the most recent pickUpTip command for the pipette used in the failed command, if any.
function getRelevantPickUpTipCommand(
- failedCommandByRunRecord: ErrorRecoveryFlowsProps['failedCommandByRunRecord'],
+ failedCommandByRunRecord: FailedCommandBySource['byRunRecord'] | null,
runCommands?: CommandsData
): Omit | null {
if (
@@ -333,9 +335,10 @@ export function getRelevantWellName(
export type GetRelevantLwLocationsParams = Pick<
UseFailedLabwareUtilsProps,
- 'runRecord' | 'failedCommandByRunRecord'
+ 'runRecord'
> & {
failedLabware: UseFailedLabwareUtilsResult['failedLabware']
+ failedCommandByRunRecord: FailedCommandBySource['byRunRecord'] | null
}
export function useRelevantFailedLwLocations({
diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useFailedPipetteUtils.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useFailedPipetteUtils.ts
index f997592f8cd..5535cd46799 100644
--- a/app/src/organisms/ErrorRecoveryFlows/hooks/useFailedPipetteUtils.ts
+++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useFailedPipetteUtils.ts
@@ -8,7 +8,7 @@ import type {
Run,
PipetteData,
} from '@opentrons/api-client'
-import type { ErrorRecoveryFlowsProps } from '/app/organisms/ErrorRecoveryFlows'
+import type { FailedCommandBySource } from './useRetainedFailedCommandBySource'
export interface UseFailedPipetteUtilsParams
extends UseFailedCommandPipetteInfoProps {
@@ -61,7 +61,7 @@ export function useFailedPipetteUtils(
interface UseFailedCommandPipetteInfoProps {
runRecord: Run | undefined
attachedInstruments: Instruments | undefined
- failedCommandByRunRecord: ErrorRecoveryFlowsProps['failedCommandByRunRecord']
+ failedCommandByRunRecord: FailedCommandBySource['byRunRecord'] | null
}
// /instruments data for the pipette used in the failedCommand, if any.
diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryCommands.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryCommands.ts
index 7614dec4be3..d6190eaa16d 100644
--- a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryCommands.ts
+++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryCommands.ts
@@ -36,11 +36,13 @@ import type { UseRouteUpdateActionsResult } from './useRouteUpdateActions'
import type { RecoveryToasts } from './useRecoveryToasts'
import type { UseRecoveryAnalyticsResult } from '/app/redux-resources/analytics'
import type { CurrentRecoveryOptionUtils } from './useRecoveryRouting'
-import type { ErrorRecoveryFlowsProps } from '../index'
+import type { ErrorRecoveryFlowsProps } from '..'
+import type { FailedCommandBySource } from './useRetainedFailedCommandBySource'
interface UseRecoveryCommandsParams {
runId: string
- failedCommandByRunRecord: ErrorRecoveryFlowsProps['failedCommandByRunRecord']
+ failedCommand: FailedCommandBySource | null
+ unvalidatedFailedCommand: ErrorRecoveryFlowsProps['unvalidatedFailedCommand']
failedLabwareUtils: UseFailedLabwareUtilsResult
routeUpdateActions: UseRouteUpdateActionsResult
recoveryToastUtils: RecoveryToasts
@@ -76,7 +78,8 @@ export interface UseRecoveryCommandsResult {
// Returns commands with a "fixit" intent. Commands may or may not terminate Error Recovery. See each command docstring for details.
export function useRecoveryCommands({
runId,
- failedCommandByRunRecord,
+ failedCommand,
+ unvalidatedFailedCommand,
failedLabwareUtils,
routeUpdateActions,
recoveryToastUtils,
@@ -88,7 +91,7 @@ export function useRecoveryCommands({
const { proceedToRouteAndStep } = routeUpdateActions
const { chainRunCommands } = useChainRunCommands(
runId,
- failedCommandByRunRecord?.id
+ unvalidatedFailedCommand?.id
)
const {
mutateAsync: resumeRunFromRecovery,
@@ -137,23 +140,23 @@ export function useRecoveryCommands({
IN_PLACE_COMMAND_TYPES.includes(
(failedCommand as InPlaceCommand).commandType
)
- return failedCommandByRunRecord != null
- ? isInPlace(failedCommandByRunRecord)
- ? failedCommandByRunRecord.error?.isDefined &&
- failedCommandByRunRecord.error?.errorType === 'overpressure' &&
+ return unvalidatedFailedCommand != null
+ ? isInPlace(unvalidatedFailedCommand)
+ ? unvalidatedFailedCommand.error?.isDefined &&
+ unvalidatedFailedCommand.error?.errorType === 'overpressure' &&
// Paranoia: this value comes from the wire and may be unevenly implemented
- typeof failedCommandByRunRecord.error?.errorInfo?.retryLocation?.at(
+ typeof unvalidatedFailedCommand.error?.errorInfo?.retryLocation?.at(
0
) === 'number'
? {
commandType: 'moveToCoordinates',
intent: 'fixit',
params: {
- pipetteId: failedCommandByRunRecord.params?.pipetteId,
+ pipetteId: unvalidatedFailedCommand.params?.pipetteId,
coordinates: {
- x: failedCommandByRunRecord.error.errorInfo.retryLocation[0],
- y: failedCommandByRunRecord.error.errorInfo.retryLocation[1],
- z: failedCommandByRunRecord.error.errorInfo.retryLocation[2],
+ x: unvalidatedFailedCommand.error.errorInfo.retryLocation[0],
+ y: unvalidatedFailedCommand.error.errorInfo.retryLocation[1],
+ z: unvalidatedFailedCommand.error.errorInfo.retryLocation[2],
},
},
}
@@ -163,7 +166,7 @@ export function useRecoveryCommands({
}
const retryFailedCommand = useCallback((): Promise => {
- const { commandType, params } = failedCommandByRunRecord as FailedCommand // Null case is handled before command could be issued.
+ const { commandType, params } = unvalidatedFailedCommand as FailedCommand // Null case is handled before command could be issued.
return chainRunRecoveryCommands(
[
// move back to the location of the command if it is an in-place command
@@ -171,7 +174,7 @@ export function useRecoveryCommands({
{ commandType, params }, // retry the command that failed
].filter(c => c != null) as CreateCommand[]
) // the created command is the same command that failed
- }, [chainRunRecoveryCommands, failedCommandByRunRecord?.key])
+ }, [chainRunRecoveryCommands, unvalidatedFailedCommand?.key])
// Homes the Z-axis of all attached pipettes.
const homePipetteZAxes = useCallback((): Promise => {
@@ -184,7 +187,7 @@ export function useRecoveryCommands({
const pickUpTipCmd = buildPickUpTips(
selectedTipLocations,
- failedCommandByRunRecord,
+ unvalidatedFailedCommand,
failedLabware
)
@@ -193,7 +196,7 @@ export function useRecoveryCommands({
} else {
return chainRunRecoveryCommands([pickUpTipCmd])
}
- }, [chainRunRecoveryCommands, failedCommandByRunRecord, failedLabwareUtils])
+ }, [chainRunRecoveryCommands, unvalidatedFailedCommand, failedLabwareUtils])
const ignoreErrorKindThisRun = (ignoreErrors: boolean): Promise => {
setIgnoreErrors(ignoreErrors)
@@ -204,16 +207,16 @@ export function useRecoveryCommands({
// If the request to update the policy fails, route to the error modal.
const handleIgnoringErrorKind = useCallback((): Promise => {
if (ignoreErrors) {
- if (failedCommandByRunRecord?.error != null) {
+ if (unvalidatedFailedCommand?.error != null) {
const ifMatch: IfMatchType = isAssumeFalsePositiveResumeKind(
- failedCommandByRunRecord
+ failedCommand
)
? 'assumeFalsePositiveAndContinue'
: 'ignoreAndContinue'
const ignorePolicyRules = buildIgnorePolicyRules(
- failedCommandByRunRecord.commandType,
- failedCommandByRunRecord.error.errorType,
+ unvalidatedFailedCommand.commandType,
+ unvalidatedFailedCommand.error.errorType,
ifMatch
)
@@ -232,8 +235,8 @@ export function useRecoveryCommands({
return Promise.resolve()
}
}, [
- failedCommandByRunRecord?.error?.errorType,
- failedCommandByRunRecord?.commandType,
+ unvalidatedFailedCommand?.error?.errorType,
+ unvalidatedFailedCommand?.commandType,
ignoreErrors,
])
@@ -262,7 +265,7 @@ export function useRecoveryCommands({
}, [runId])
const handleResumeAction = (): Promise => {
- if (isAssumeFalsePositiveResumeKind(failedCommandByRunRecord)) {
+ if (isAssumeFalsePositiveResumeKind(failedCommand)) {
return resumeRunFromRecoveryAssumingFalsePositive(runId)
} else {
return resumeRunFromRecovery(runId)
@@ -302,14 +305,14 @@ export function useRecoveryCommands({
const moveLabwareWithoutPause = useCallback((): Promise => {
const moveLabwareCmd = buildMoveLabwareWithoutPause(
- failedCommandByRunRecord
+ unvalidatedFailedCommand
)
if (moveLabwareCmd == null) {
return Promise.reject(new Error('Invalid use of MoveLabware command'))
} else {
return chainRunRecoveryCommands([moveLabwareCmd])
}
- }, [chainRunRecoveryCommands, failedCommandByRunRecord])
+ }, [chainRunRecoveryCommands, unvalidatedFailedCommand])
return {
resumeRun,
@@ -326,9 +329,9 @@ export function useRecoveryCommands({
}
export function isAssumeFalsePositiveResumeKind(
- failedCommandByRunRecord: UseRecoveryCommandsParams['failedCommandByRunRecord']
+ failedCommand: UseRecoveryCommandsParams['failedCommand']
): boolean {
- const errorKind = getErrorKind(failedCommandByRunRecord)
+ const errorKind = getErrorKind(failedCommand)
switch (errorKind) {
case ERROR_KINDS.TIP_NOT_DETECTED:
diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryToasts.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryToasts.ts
index 2edf732bfdd..6daf6998ae5 100644
--- a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryToasts.ts
+++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryToasts.ts
@@ -10,7 +10,7 @@ import type { UseCommandTextStringParams } from '/app/local-resources/commands'
export type BuildToast = Omit & {
isOnDevice: boolean
- currentStepCount: StepCounts['currentStepNumber']
+ stepCounts: StepCounts
selectedRecoveryOption: CurrentRecoveryOptionUtils['selectedRecoveryOption']
}
@@ -21,15 +21,16 @@ export interface RecoveryToasts {
// Provides methods for rendering success/failure toasts after performing a terminal recovery command.
export function useRecoveryToasts({
- currentStepCount,
+ stepCounts,
isOnDevice,
selectedRecoveryOption,
...rest
}: BuildToast): RecoveryToasts {
+ const { currentStepNumber, hasRunDiverged } = stepCounts
const { makeToast } = useToaster()
const displayType = isOnDevice ? 'odd' : 'desktop'
- const stepNumber = getStepNumber(selectedRecoveryOption, currentStepCount)
+ const stepNumber = getStepNumber(selectedRecoveryOption, currentStepNumber)
const desktopFullCommandText = useRecoveryFullCommandText({
...rest,
@@ -46,7 +47,8 @@ export function useRecoveryToasts({
? desktopFullCommandText
: recoveryToastText
// The "heading" of the toast message. Currently, this text is only present on the desktop toasts.
- const headingText = displayType === 'desktop' ? recoveryToastText : undefined
+ const headingText =
+ displayType === 'desktop' && !hasRunDiverged ? recoveryToastText : undefined
const makeSuccessToast = (): void => {
if (selectedRecoveryOption !== RECOVERY_MAP.CANCEL_RUN.ROUTE) {
@@ -73,12 +75,18 @@ export function useRecoveryToastText({
}): string {
const { t } = useTranslation('error_recovery')
- const currentStepReturnVal = t('retrying_step_succeeded', {
- step: stepNumber,
- }) as string
- const nextStepReturnVal = t('skipping_to_step_succeeded', {
- step: stepNumber,
- }) as string
+ const currentStepReturnVal =
+ stepNumber != null
+ ? t('retrying_step_succeeded', {
+ step: stepNumber,
+ })
+ : t('retrying_step_succeeded_na')
+ const nextStepReturnVal =
+ stepNumber != null
+ ? t('skipping_to_step_succeeded', {
+ step: stepNumber,
+ })
+ : t('skipping_to_step_succeeded_na')
const toastText = handleRecoveryOptionAction(
selectedRecoveryOption,
@@ -102,7 +110,7 @@ export function useRecoveryFullCommandText(
): string | null {
const { commandTextData, stepNumber } = props
- const relevantCmdIdx = typeof stepNumber === 'number' ? stepNumber : -1
+ const relevantCmdIdx = stepNumber ?? -1
const relevantCmd = commandTextData?.commands[relevantCmdIdx] ?? null
const { commandText, kind } = useCommandTextString({
@@ -110,8 +118,8 @@ export function useRecoveryFullCommandText(
command: relevantCmd,
})
- if (typeof stepNumber === 'string') {
- return stepNumber
+ if (stepNumber == null) {
+ return null
}
// Occurs when the relevantCmd doesn't exist, ex, we "skip" the last command of a run.
else if (relevantCmd === null) {
@@ -129,12 +137,12 @@ export function useRecoveryFullCommandText(
// Return the user-facing step number, 0 indexed. If the step number cannot be determined, return '?'.
export function getStepNumber(
selectedRecoveryOption: BuildToast['selectedRecoveryOption'],
- currentStepCount: BuildToast['currentStepCount']
-): number | string {
- const currentStepReturnVal = currentStepCount ?? '?'
+ currentStepCount: BuildToast['stepCounts']['currentStepNumber']
+): number | null {
+ const currentStepReturnVal = currentStepCount ?? null
// There is always a next protocol step after a command that can error, therefore, we don't need to handle that.
const nextStepReturnVal =
- typeof currentStepCount === 'number' ? currentStepCount + 1 : '?'
+ typeof currentStepCount === 'number' ? currentStepCount + 1 : null
return handleRecoveryOptionAction(
selectedRecoveryOption,
@@ -149,7 +157,7 @@ function handleRecoveryOptionAction(
selectedRecoveryOption: CurrentRecoveryOptionUtils['selectedRecoveryOption'],
currentStepReturnVal: T,
nextStepReturnVal: T
-): T | string {
+): T | null {
switch (selectedRecoveryOption) {
case RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE:
case RECOVERY_MAP.SKIP_STEP_WITH_SAME_TIPS.ROUTE:
@@ -163,8 +171,9 @@ function handleRecoveryOptionAction(
case RECOVERY_MAP.RETRY_STEP.ROUTE:
case RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE:
return currentStepReturnVal
- default:
- return 'HANDLE RECOVERY TOAST OPTION EXPLICITLY.'
+ default: {
+ return null
+ }
}
}
diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useRetainedFailedCommandBySource.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useRetainedFailedCommandBySource.ts
index c967d4968b1..90afa5851da 100644
--- a/app/src/organisms/ErrorRecoveryFlows/hooks/useRetainedFailedCommandBySource.ts
+++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useRetainedFailedCommandBySource.ts
@@ -16,7 +16,7 @@ export interface FailedCommandBySource {
* In order to reduce misuse, bundle the failedCommand into "run" and "analysis" versions.
*/
export function useRetainedFailedCommandBySource(
- failedCommandByRunRecord: ErrorRecoveryFlowsProps['failedCommandByRunRecord'],
+ failedCommandByRunRecord: ErrorRecoveryFlowsProps['unvalidatedFailedCommand'],
protocolAnalysis: ErrorRecoveryFlowsProps['protocolAnalysis']
): FailedCommandBySource | null {
// In some cases, Error Recovery (by the app definition) persists when Error Recovery (by the server definition) does
diff --git a/app/src/organisms/ErrorRecoveryFlows/index.tsx b/app/src/organisms/ErrorRecoveryFlows/index.tsx
index 124c4fea65f..3ec1afed3d8 100644
--- a/app/src/organisms/ErrorRecoveryFlows/index.tsx
+++ b/app/src/organisms/ErrorRecoveryFlows/index.tsx
@@ -109,17 +109,21 @@ export function useErrorRecoveryFlows(
export interface ErrorRecoveryFlowsProps {
runId: string
runStatus: RunStatus | null
- failedCommandByRunRecord: FailedCommand | null
+ /* In some parts of Error Recovery, such as "retry failed command" during a generic error flow, we want to utilize
+ * information derived from the failed command from the run record even if there is no matching command in protocol analysis.
+ * Using a failed command that is not matched to a protocol analysis command is unsafe in most circumstances (ie, in
+ * non-generic recovery flows. Prefer using failedCommandBySource in most circumstances. */
+ unvalidatedFailedCommand: FailedCommand | null
protocolAnalysis: CompletedProtocolAnalysis | null
}
export function ErrorRecoveryFlows(
props: ErrorRecoveryFlowsProps
): JSX.Element | null {
- const { protocolAnalysis, runStatus, failedCommandByRunRecord } = props
+ const { protocolAnalysis, runStatus, unvalidatedFailedCommand } = props
const failedCommandBySource = useRetainedFailedCommandBySource(
- failedCommandByRunRecord,
+ unvalidatedFailedCommand,
protocolAnalysis
)
diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/ErrorDetailsModal.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/ErrorDetailsModal.tsx
index b988c83971b..7eb207a9fe7 100644
--- a/app/src/organisms/ErrorRecoveryFlows/shared/ErrorDetailsModal.tsx
+++ b/app/src/organisms/ErrorRecoveryFlows/shared/ErrorDetailsModal.tsx
@@ -44,7 +44,7 @@ export function useErrorDetailsModal(): {
type ErrorDetailsModalProps = Omit<
ErrorRecoveryFlowsProps,
- 'failedCommandByRunRecord'
+ 'unvalidatedFailedCommand'
> &
ERUtilsResults & {
toggleModal: () => void
@@ -57,7 +57,7 @@ type ErrorDetailsModalProps = Omit<
export function ErrorDetailsModal(props: ErrorDetailsModalProps): JSX.Element {
const { failedCommand, toggleModal, isOnDevice } = props
- const errorKind = getErrorKind(failedCommand?.byRunRecord ?? null)
+ const errorKind = getErrorKind(failedCommand)
const errorName = useErrorName(errorKind)
const isNotificationErrorKind = (): boolean => {
diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/StepInfo.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/StepInfo.tsx
index 8381865e3c3..bbc12ce0429 100644
--- a/app/src/organisms/ErrorRecoveryFlows/shared/StepInfo.tsx
+++ b/app/src/organisms/ErrorRecoveryFlows/shared/StepInfo.tsx
@@ -30,7 +30,7 @@ export function StepInfo({
...styleProps
}: StepInfoProps): JSX.Element {
const { t } = useTranslation('error_recovery')
- const { currentStepNumber, totalStepCount } = stepCounts
+ const { currentStepNumber, totalStepCount, hasRunDiverged } = stepCounts
const currentCopy = currentStepNumber ?? '?'
const totalCopy = totalStepCount ?? '?'
@@ -38,6 +38,11 @@ export function StepInfo({
const desktopStyleDefaulted = desktopStyle ?? 'bodyDefaultRegular'
const oddStyleDefaulted = oddStyle ?? 'bodyTextRegular'
+ const buildAtStepCopy = (): string =>
+ hasRunDiverged
+ ? `${t('at_step')}: N/A`
+ : `${t('at_step')} ${currentCopy}/${totalCopy}: `
+
return (
- {`${t('at_step')} ${currentCopy}/${totalCopy}: `}
+ {buildAtStepCopy()}
{failedCommand?.byAnalysis != null && protocolAnalysis != null ? (
-
+ {!props.stepCounts.hasRunDiverged ? (
+
+ ) : (
+
+ )}
{
resumeRun: mockResumeRun,
} as any,
errorKind: ERROR_KINDS.GENERAL_ERROR,
+ stepCounts: { hasRunDiverged: false },
} as any
})
diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/SkipStepInfo.test.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/SkipStepInfo.test.tsx
index e1ac4cf1adc..28ef4177648 100644
--- a/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/SkipStepInfo.test.tsx
+++ b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/SkipStepInfo.test.tsx
@@ -28,6 +28,7 @@ describe('SkipStepInfo', () => {
currentRecoveryOptionUtils: {
selectedRecoveryOption: RECOVERY_MAP.SKIP_STEP_WITH_SAME_TIPS.ROUTE,
} as any,
+ stepCounts: { hasRunDiverged: false },
} as any
})
diff --git a/app/src/organisms/ErrorRecoveryFlows/utils/__tests__/getErrorKind.test.ts b/app/src/organisms/ErrorRecoveryFlows/utils/__tests__/getErrorKind.test.ts
index fb9eea82c63..e9b5722ffa8 100644
--- a/app/src/organisms/ErrorRecoveryFlows/utils/__tests__/getErrorKind.test.ts
+++ b/app/src/organisms/ErrorRecoveryFlows/utils/__tests__/getErrorKind.test.ts
@@ -71,13 +71,17 @@ describe('getErrorKind', () => {
])(
'returns $expectedError for $commandType with errorType $errorType',
({ commandType, errorType, expectedError, isDefined = true }) => {
- const result = getErrorKind({
+ const runRecordFailedCommand = {
commandType,
error: {
isDefined,
errorType,
} as RunCommandError,
- } as RunTimeCommand)
+ } as RunTimeCommand
+
+ const result = getErrorKind({
+ byRunRecord: runRecordFailedCommand,
+ } as any)
expect(result).toEqual(expectedError)
}
)
diff --git a/app/src/organisms/ErrorRecoveryFlows/utils/getErrorKind.ts b/app/src/organisms/ErrorRecoveryFlows/utils/getErrorKind.ts
index 30fc4783473..1dc5e023a6c 100644
--- a/app/src/organisms/ErrorRecoveryFlows/utils/getErrorKind.ts
+++ b/app/src/organisms/ErrorRecoveryFlows/utils/getErrorKind.ts
@@ -1,18 +1,24 @@
import { ERROR_KINDS, DEFINED_ERROR_TYPES } from '../constants'
-import type { RunTimeCommand } from '@opentrons/shared-data'
import type { ErrorKind } from '../types'
+import type { FailedCommandBySource } from '/app/organisms/ErrorRecoveryFlows/hooks'
/**
* Given server-side information about a failed command,
* decide which UI flow to present to recover from it.
+ *
+ * NOTE IMPORTANT: Any failed command by run record must have an equivalent protocol analysis command or default
+ * to the fallback general error. Prefer using FailedCommandBySource for this reason.
*/
-export function getErrorKind(failedCommand: RunTimeCommand | null): ErrorKind {
- const commandType = failedCommand?.commandType
- const errorIsDefined = failedCommand?.error?.isDefined ?? false
- const errorType = failedCommand?.error?.errorType
+export function getErrorKind(
+ failedCommand: FailedCommandBySource | null
+): ErrorKind {
+ const failedCommandByRunRecord = failedCommand?.byRunRecord ?? null
+ const commandType = failedCommandByRunRecord?.commandType
+ const errorIsDefined = failedCommandByRunRecord?.error?.isDefined ?? false
+ const errorType = failedCommandByRunRecord?.error?.errorType
- if (errorIsDefined) {
+ if (Boolean(errorIsDefined)) {
if (
commandType === 'prepareToAspirate' &&
errorType === DEFINED_ERROR_TYPES.OVERPRESSURE
diff --git a/app/src/organisms/ODD/RunningProtocol/CurrentRunningProtocolCommand.tsx b/app/src/organisms/ODD/RunningProtocol/CurrentRunningProtocolCommand.tsx
index be9e5e25cbb..2866c3f95dd 100644
--- a/app/src/organisms/ODD/RunningProtocol/CurrentRunningProtocolCommand.tsx
+++ b/app/src/organisms/ODD/RunningProtocol/CurrentRunningProtocolCommand.tsx
@@ -172,13 +172,14 @@ export function CurrentRunningProtocolCommand({
}
const currentRunStatus = t(`status_${runStatus}`)
- const { currentStepNumber, totalStepCount } = useRunningStepCounts(
- runId,
- mostRecentCommandData
- )
- const stepCounterCopy = `${t('step')} ${currentStepNumber ?? '?'}/${
- totalStepCount ?? '?'
- }`
+ const {
+ currentStepNumber,
+ totalStepCount,
+ hasRunDiverged,
+ } = useRunningStepCounts(runId, mostRecentCommandData)
+ const stepCounterCopy = hasRunDiverged
+ ? `${t('step_na')}`
+ : `${t('step')} ${currentStepNumber ?? '?'}/${totalStepCount ?? '?'}`
const onStop = (): void => {
if (runStatus === RUN_STATUS_RUNNING) pauseRun()
diff --git a/app/src/organisms/ODD/RunningProtocol/__tests__/CurrentRunningProtocolCommand.test.tsx b/app/src/organisms/ODD/RunningProtocol/__tests__/CurrentRunningProtocolCommand.test.tsx
index 54d241ff9af..581df7c013c 100644
--- a/app/src/organisms/ODD/RunningProtocol/__tests__/CurrentRunningProtocolCommand.test.tsx
+++ b/app/src/organisms/ODD/RunningProtocol/__tests__/CurrentRunningProtocolCommand.test.tsx
@@ -125,7 +125,7 @@ describe('CurrentRunningProtocolCommand', () => {
})
render(props)
- screen.getByText('Step ?/?')
+ screen.getByText('Step: N/A')
})
// ToDo (kj:04/10/2023) once we fix the track event stuff, we can implement tests
diff --git a/app/src/pages/ODD/RunningProtocol/index.tsx b/app/src/pages/ODD/RunningProtocol/index.tsx
index 4c63302564e..33d9d515930 100644
--- a/app/src/pages/ODD/RunningProtocol/index.tsx
+++ b/app/src/pages/ODD/RunningProtocol/index.tsx
@@ -168,7 +168,7 @@ export function RunningProtocol(): JSX.Element {
) : null}
diff --git a/app/src/resources/runs/useMostRecentCompletedAnalysis.ts b/app/src/resources/runs/useMostRecentCompletedAnalysis.ts
index e5188af8d38..7a3f9eefb7e 100644
--- a/app/src/resources/runs/useMostRecentCompletedAnalysis.ts
+++ b/app/src/resources/runs/useMostRecentCompletedAnalysis.ts
@@ -8,7 +8,6 @@ import { useNotifyRunQuery } from '/app/resources/runs'
import type { CompletedProtocolAnalysis } from '@opentrons/shared-data'
-// TODO(jh, 06-17-24): This is used elsewhere in the app and should probably live in something like resources.
export function useMostRecentCompletedAnalysis(
runId: string | null
): CompletedProtocolAnalysis | null {