Skip to content

Commit 972c592

Browse files
authored
fix(app): add affordances for tip detection failures (#16828)
Closes RQA-3589 At the end of a protocol run, we execute tip detection logic to determine whether or not we should pop drop tip CTAs. There are a few network requests necessary to make this work, and we are not error handling if any of these requests fail. This PR adds a general fallback: if almost any network request fails, let's just assume both pipettes have tips attached and pop the CTAs (assuming the pipettes have ok firmware status). It's probably better to be more conservative here, and in practice, this exact scenario should occur rarely. Note that the very first request we make is to get the currently attached instruments. If this request fails, we can't do drop tip wizard, so we shouldn't show CTAs. The only workaround here would be going to design and coming up with some sort of "can't detect tips" copy. At the very least, the user can always go to drop tip wizard manually and handle tips there.
1 parent 9bf09d8 commit 972c592

File tree

2 files changed

+80
-15
lines changed

2 files changed

+80
-15
lines changed

app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/__tests__/getPipettesWithTipAttached.test.ts

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,21 @@ const DROP_TIP_IN_PLACE = 'dropTipInPlace'
2020
const LOAD_PIPETTE = 'loadPipette'
2121
const FIXIT_INTENT = 'fixit'
2222

23+
const LEFT_PIPETTE = {
24+
mount: LEFT,
25+
state: { tipDetected: true },
26+
instrumentType: 'pipette',
27+
ok: true,
28+
}
29+
const RIGHT_PIPETTE = {
30+
mount: RIGHT,
31+
state: { tipDetected: true },
32+
instrumentType: 'pipette',
33+
ok: true,
34+
}
35+
2336
const mockAttachedInstruments = {
24-
data: [
25-
{ mount: LEFT, state: { tipDetected: true } },
26-
{ mount: RIGHT, state: { tipDetected: true } },
27-
],
37+
data: [LEFT_PIPETTE, RIGHT_PIPETTE],
2838
meta: { cursor: 0, totalLength: 2 },
2939
}
3040

@@ -185,4 +195,45 @@ describe('getPipettesWithTipAttached', () => {
185195
const result = await getPipettesWithTipAttached(DEFAULT_PARAMS)
186196
expect(result).toEqual([mockAttachedInstruments.data[0]])
187197
})
198+
199+
it('returns all valid attached pipettes when an error occurs', async () => {
200+
vi.mocked(getCommands).mockRejectedValueOnce(
201+
new Error('Example network error')
202+
)
203+
204+
const result = await getPipettesWithTipAttached(DEFAULT_PARAMS)
205+
206+
expect(result).toEqual([LEFT_PIPETTE, RIGHT_PIPETTE])
207+
})
208+
209+
it('filters out not ok pipettes', async () => {
210+
vi.mocked(getCommands).mockRejectedValueOnce(new Error('Network error'))
211+
212+
const mockInvalidPipettes = {
213+
data: [
214+
LEFT_PIPETTE,
215+
{
216+
...RIGHT_PIPETTE,
217+
ok: false,
218+
},
219+
],
220+
meta: { cursor: 0, totalLength: 2 },
221+
}
222+
223+
const params = {
224+
...DEFAULT_PARAMS,
225+
attachedInstruments: mockInvalidPipettes as any,
226+
}
227+
228+
const result = await getPipettesWithTipAttached(params)
229+
230+
expect(result).toEqual([
231+
{
232+
mount: LEFT,
233+
state: { tipDetected: true },
234+
instrumentType: 'pipette',
235+
ok: true,
236+
},
237+
])
238+
})
188239
})

app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/getPipettesWithTipAttached.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,17 @@ export function getPipettesWithTipAttached({
3131
return Promise.resolve([])
3232
}
3333

34-
return getCommandsExecutedDuringRun(
35-
host as HostConfig,
36-
runId
37-
).then(executedCmdData =>
38-
checkPipettesForAttachedTips(
39-
executedCmdData.data,
40-
runRecord.data.pipettes,
41-
attachedInstruments.data as PipetteData[]
42-
)
34+
return (
35+
getCommandsExecutedDuringRun(host as HostConfig, runId)
36+
.then(executedCmdData =>
37+
checkPipettesForAttachedTips(
38+
executedCmdData.data,
39+
runRecord.data.pipettes,
40+
attachedInstruments.data as PipetteData[]
41+
)
42+
)
43+
// If any network error occurs, return all attached pipettes as having tips attached for safety reasons.
44+
.catch(() => Promise.resolve(getPipettesDataFrom(attachedInstruments)))
4345
)
4446
}
4547

@@ -119,8 +121,10 @@ function checkPipettesForAttachedTips(
119121
}
120122

121123
// Convert the array of mounts with attached tips to PipetteData with attached tips.
122-
const pipettesWithTipAttached = attachedPipettes.filter(attachedPipette =>
123-
mountsWithTipAttached.includes(attachedPipette.mount)
124+
const pipettesWithTipAttached = attachedPipettes.filter(
125+
attachedPipette =>
126+
mountsWithTipAttached.includes(attachedPipette.mount) &&
127+
attachedPipette.ok
124128
)
125129

126130
// Preferentially assign the left mount as the first element.
@@ -136,3 +140,13 @@ function checkPipettesForAttachedTips(
136140

137141
return pipettesWithTipAttached
138142
}
143+
144+
function getPipettesDataFrom(
145+
attachedInstruments: Instruments | null
146+
): PipetteData[] {
147+
return attachedInstruments != null
148+
? (attachedInstruments.data.filter(
149+
instrument => instrument.instrumentType === 'pipette' && instrument.ok
150+
) as PipetteData[])
151+
: []
152+
}

0 commit comments

Comments
 (0)