Skip to content

Commit 7e6c8ee

Browse files
fix(api-client, react-api-client, app, robot-server): support multiple recovery policies during a run (#16950)
Closes RQA-3670 We need to support the client ignoring several different classes of errors in a single run, which means the app needs to know the current recovery policy and be able to modify. This PR adds the necessary infrastructure to support that. Co-authored-by: Max Marrone <[email protected]>
1 parent 64aaeb9 commit 7e6c8ee

File tree

18 files changed

+396
-127
lines changed

18 files changed

+396
-127
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { GET, request } from '../request'
2+
3+
import type { HostConfig } from '../types'
4+
import type { ResponsePromise } from '../request'
5+
import type { ErrorRecoveryPolicyResponse } from './types'
6+
7+
export function getErrorRecoveryPolicy(
8+
config: HostConfig,
9+
runId: string
10+
): ResponsePromise<ErrorRecoveryPolicyResponse> {
11+
return request<ErrorRecoveryPolicyResponse>(
12+
GET,
13+
`/runs/${runId}/errorRecoveryPolicy`,
14+
null,
15+
config
16+
)
17+
}

api-client/src/runs/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export * from './createLabwareOffset'
1515
export * from './createLabwareDefinition'
1616
export * from './constants'
1717
export * from './updateErrorRecoveryPolicy'
18+
export * from './getErrorRecoveryPolicy'
1819

1920
export * from './types'
2021
export type { CreateRunData } from './createRun'

api-client/src/runs/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ export interface UpdateErrorRecoveryPolicyRequest {
204204
}
205205

206206
export type UpdateErrorRecoveryPolicyResponse = Record<string, never>
207+
export type ErrorRecoveryPolicyResponse = UpdateErrorRecoveryPolicyRequest
207208

208209
/**
209210
* Current Run State Data

app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryCommands.test.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import { renderHook, act } from '@testing-library/react'
44
import {
55
useResumeRunFromRecoveryMutation,
66
useStopRunMutation,
7-
useUpdateErrorRecoveryPolicy,
87
useResumeRunFromRecoveryAssumingFalsePositiveMutation,
98
} from '@opentrons/react-api-client'
109

11-
import { useChainRunCommands } from '/app/resources/runs'
10+
import {
11+
useChainRunCommands,
12+
useUpdateRecoveryPolicyWithStrategy,
13+
} from '/app/resources/runs'
1214
import {
1315
useRecoveryCommands,
1416
HOME_PIPETTE_Z_AXES,
@@ -80,9 +82,9 @@ describe('useRecoveryCommands', () => {
8082
vi.mocked(useChainRunCommands).mockReturnValue({
8183
chainRunCommands: mockChainRunCommands,
8284
} as any)
83-
vi.mocked(useUpdateErrorRecoveryPolicy).mockReturnValue({
84-
mutateAsync: mockUpdateErrorRecoveryPolicy,
85-
} as any)
85+
vi.mocked(useUpdateRecoveryPolicyWithStrategy).mockReturnValue(
86+
mockUpdateErrorRecoveryPolicy as any
87+
)
8688
vi.mocked(
8789
useResumeRunFromRecoveryAssumingFalsePositiveMutation
8890
).mockReturnValue({
@@ -361,7 +363,8 @@ describe('useRecoveryCommands', () => {
361363
)
362364

363365
expect(mockUpdateErrorRecoveryPolicy).toHaveBeenCalledWith(
364-
expectedPolicyRules
366+
expectedPolicyRules,
367+
'append'
365368
)
366369
})
367370

app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryCommands.ts

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import head from 'lodash/head'
44
import {
55
useResumeRunFromRecoveryMutation,
66
useStopRunMutation,
7-
useUpdateErrorRecoveryPolicy,
87
useResumeRunFromRecoveryAssumingFalsePositiveMutation,
98
} from '@opentrons/react-api-client'
109

11-
import { useChainRunCommands } from '/app/resources/runs'
10+
import {
11+
useChainRunCommands,
12+
useUpdateRecoveryPolicyWithStrategy,
13+
} from '/app/resources/runs'
1214
import { DEFINED_ERROR_TYPES, ERROR_KINDS, RECOVERY_MAP } from '../constants'
1315
import { getErrorKind } from '/app/organisms/ErrorRecoveryFlows/utils'
1416

@@ -23,12 +25,7 @@ import type {
2325
PrepareToAspirateRunTimeCommand,
2426
MoveLabwareParams,
2527
} from '@opentrons/shared-data'
26-
import type {
27-
CommandData,
28-
IfMatchType,
29-
RecoveryPolicyRulesParams,
30-
RunAction,
31-
} from '@opentrons/api-client'
28+
import type { CommandData, IfMatchType, RunAction } from '@opentrons/api-client'
3229
import type { WellGroup } from '@opentrons/components'
3330
import type { FailedCommand, RecoveryRoute, RouteStep } from '../types'
3431
import type { UseFailedLabwareUtilsResult } from './useFailedLabwareUtils'
@@ -38,6 +35,7 @@ import type { UseRecoveryAnalyticsResult } from '/app/redux-resources/analytics'
3835
import type { CurrentRecoveryOptionUtils } from './useRecoveryRouting'
3936
import type { ErrorRecoveryFlowsProps } from '..'
4037
import type { FailedCommandBySource } from './useRetainedFailedCommandBySource'
38+
import type { UpdateErrorRecoveryPolicyWithStrategy } from '/app/resources/runs'
4139

4240
interface UseRecoveryCommandsParams {
4341
runId: string
@@ -100,9 +98,7 @@ export function useRecoveryCommands({
10098
mutateAsync: resumeRunFromRecoveryAssumingFalsePositive,
10199
} = useResumeRunFromRecoveryAssumingFalsePositiveMutation()
102100
const { stopRun } = useStopRunMutation()
103-
const {
104-
mutateAsync: updateErrorRecoveryPolicy,
105-
} = useUpdateErrorRecoveryPolicy(runId)
101+
const updateErrorRecoveryPolicy = useUpdateRecoveryPolicyWithStrategy(runId)
106102
const { makeSuccessToast } = recoveryToastUtils
107103

108104
// TODO(jh, 11-21-24): Some commands return a 200 with an error body. We should catch these and propagate the error.
@@ -231,10 +227,12 @@ export function useRecoveryCommands({
231227
ifMatch
232228
)
233229

234-
return updateErrorRecoveryPolicy(ignorePolicyRules)
230+
return updateErrorRecoveryPolicy(ignorePolicyRules, 'append')
235231
.then(() => Promise.resolve())
236-
.catch(() =>
237-
Promise.reject(new Error('Failed to update recovery policy.'))
232+
.catch((e: Error) =>
233+
Promise.reject(
234+
new Error(`Failed to update recovery policy: ${e.message}`)
235+
)
238236
)
239237
} else {
240238
void proceedToRouteAndStep(RECOVERY_MAP.ERROR_WHILE_RECOVERING.ROUTE)
@@ -421,12 +419,8 @@ export const buildIgnorePolicyRules = (
421419
commandType: FailedCommand['commandType'],
422420
errorType: string,
423421
ifMatch: IfMatchType
424-
): RecoveryPolicyRulesParams => {
425-
return [
426-
{
427-
commandType,
428-
errorType,
429-
ifMatch,
430-
},
431-
]
432-
}
422+
): UpdateErrorRecoveryPolicyWithStrategy['newPolicy'] => ({
423+
commandType,
424+
errorType,
425+
ifMatch,
426+
})

app/src/resources/runs/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ export * from './useModuleCalibrationStatus'
2929
export * from './useProtocolAnalysisErrors'
3030
export * from './useLastRunCommand'
3131
export * from './useRunStatuses'
32+
export * from './useUpdateRecoveryPolicyWithStrategy'
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import {
2+
useHost,
3+
useUpdateErrorRecoveryPolicy,
4+
} from '@opentrons/react-api-client'
5+
import { getErrorRecoveryPolicy } from '@opentrons/api-client'
6+
7+
import type {
8+
HostConfig,
9+
RecoveryPolicyRulesParams,
10+
UpdateErrorRecoveryPolicyResponse,
11+
} from '@opentrons/api-client'
12+
13+
/**
14+
* append - Add a new policy rule to the end of the existing recovery policy.
15+
*/
16+
export type UpdatePolicyStrategy = 'append'
17+
18+
export interface UpdateErrorRecoveryPolicyWithStrategy {
19+
runId: string
20+
newPolicy: RecoveryPolicyRulesParams[number]
21+
strategy: UpdatePolicyStrategy
22+
}
23+
24+
export function useUpdateRecoveryPolicyWithStrategy(
25+
runId: string
26+
): (
27+
newPolicy: UpdateErrorRecoveryPolicyWithStrategy['newPolicy'],
28+
strategy: UpdateErrorRecoveryPolicyWithStrategy['strategy']
29+
) => Promise<UpdateErrorRecoveryPolicyResponse> {
30+
const host = useHost()
31+
32+
const {
33+
mutateAsync: updateErrorRecoveryPolicy,
34+
} = useUpdateErrorRecoveryPolicy(runId)
35+
36+
return (
37+
newPolicy: UpdateErrorRecoveryPolicyWithStrategy['newPolicy'],
38+
strategy: UpdateErrorRecoveryPolicyWithStrategy['strategy']
39+
) =>
40+
getErrorRecoveryPolicy(host as HostConfig, runId).then(res => {
41+
const existingPolicyRules = res.data.data.policyRules.map(rule => ({
42+
commandType: rule.matchCriteria.command.commandType,
43+
errorType: rule.matchCriteria.command.error.errorType,
44+
ifMatch: rule.ifMatch,
45+
}))
46+
47+
const buildUpdatedPolicy = (): RecoveryPolicyRulesParams => {
48+
switch (strategy) {
49+
case 'append':
50+
return [...existingPolicyRules, newPolicy]
51+
default: {
52+
console.error('Unhandled policy strategy, defaulting to append.')
53+
return [...existingPolicyRules, newPolicy]
54+
}
55+
}
56+
}
57+
58+
return updateErrorRecoveryPolicy(buildUpdatedPolicy())
59+
})
60+
}

react-api-client/src/runs/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export { useRunCommandErrors } from './useRunCommandErrors'
1919
export * from './useCreateLabwareOffsetMutation'
2020
export * from './useCreateLabwareDefinitionMutation'
2121
export * from './useUpdateErrorRecoveryPolicy'
22+
export * from './useErrorRecoveryPolicy'
2223

2324
export type { UsePlayRunMutationResult } from './usePlayRunMutation'
2425
export type { UsePauseRunMutationResult } from './usePauseRunMutation'
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useQuery } from 'react-query'
2+
3+
import { getErrorRecoveryPolicy } from '@opentrons/api-client'
4+
5+
import { useHost } from '../api'
6+
7+
import type { UseQueryOptions, UseQueryResult } from 'react-query'
8+
import type {
9+
ErrorRecoveryPolicyResponse,
10+
HostConfig,
11+
} from '@opentrons/api-client'
12+
13+
export function useErrorRecoveryPolicy(
14+
runId: string,
15+
options: UseQueryOptions<ErrorRecoveryPolicyResponse, Error> = {}
16+
): UseQueryResult<ErrorRecoveryPolicyResponse, Error> {
17+
const host = useHost()
18+
19+
const query = useQuery<ErrorRecoveryPolicyResponse, Error>(
20+
[host, 'runs', runId, 'errorRecoveryPolicy'],
21+
() =>
22+
getErrorRecoveryPolicy(host as HostConfig, runId)
23+
.then(response => response.data)
24+
.catch(e => {
25+
throw e
26+
}),
27+
options
28+
)
29+
30+
return query
31+
}

react-api-client/src/runs/useUpdateErrorRecoveryPolicy.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import type {
1616
HostConfig,
1717
} from '@opentrons/api-client'
1818

19-
export type UseUpdateErrorRecoveryPolicyResponse = UseMutationResult<
19+
export type UseErrorRecoveryPolicyResponse = UseMutationResult<
2020
UpdateErrorRecoveryPolicyResponse,
2121
AxiosError,
2222
RecoveryPolicyRulesParams
@@ -37,7 +37,7 @@ export type UseUpdateErrorRecoveryPolicyOptions = UseMutationOptions<
3737
export function useUpdateErrorRecoveryPolicy(
3838
runId: string,
3939
options: UseUpdateErrorRecoveryPolicyOptions = {}
40-
): UseUpdateErrorRecoveryPolicyResponse {
40+
): UseErrorRecoveryPolicyResponse {
4141
const host = useHost()
4242

4343
const mutation = useMutation<

0 commit comments

Comments
 (0)