Skip to content

Commit a464439

Browse files
authored
refactor(app): remove start run disablement and replace with toast (#20182)
# Overview Refactor start run disablement button to display toast description. ## Test Plan and Hands on Testing - smoke tested on the app <img width="929" height="754" alt="Screenshot 2025-11-17 at 4 47 31 PM" src="https://github.com/user-attachments/assets/ae2f7cb6-5923-4ef1-98bc-973d6edd2f5b" /> ## Changelog - removed disabled state - added new strings to support specific protocol disablement reasons. ## Review requests - review additional messages added - ensure changes I made don't mean you can start the run even if it should be disabled. ## Risk assessment - lowish Closes EXEC-1635
1 parent 252f184 commit a464439

File tree

4 files changed

+96
-85
lines changed

4 files changed

+96
-85
lines changed

app/src/assets/localization/en/run_details.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"error_type": "Error: {{errorType}}",
6969
"failed_step": "Failed step",
7070
"final_step": "Final Step",
71+
"fixture_mismatch": "Deck configuration does not match protocol requirements",
7172
"ignore_stored_data": "Ignore stored data",
7273
"image_at_step_at_timestamp": "Image at {{step}} at {{timestamp}}",
7374
"image_capture": "Image capture",
@@ -77,6 +78,7 @@
7778
"image_loading": "Image loading",
7879
"images": "Images",
7980
"images_available_download": "Image files associated with the run are available on the robot’s Recent Runs screen and Camera tab.",
81+
"instrument_calibration_incomplete": "Instrument calibration is incomplete",
8082
"labware": "labware",
8183
"labware_offset_data": "labware offset data",
8284
"left": "Left",
@@ -91,7 +93,9 @@
9193
"loading_data": "Loading data...",
9294
"loading_protocol": "Loading Protocol",
9395
"location": "location",
96+
"module_calibration_incomplete": "Module calibration is incomplete",
9497
"module_controls": "Module Controls",
98+
"modules_missing": "Modules are missing from deck hardware",
9599
"module_slot_number": "Slot {{slot_number}}",
96100
"move_labware": "Move Labware",
97101
"na": "N/A",
@@ -141,6 +145,7 @@
141145
"robot_was_recalibrated": "This robot was recalibrated after this Labware Offset data was stored.",
142146
"run": "Run",
143147
"run_again": "Run again",
148+
"run_again_disabled": "Run again is disabled for this protocol",
144149
"run_canceled": "Run canceled.",
145150
"run_canceled_splash": "Run canceled",
146151
"run_canceled_with_errors": "Run canceled with errors.",

app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/ActionButton/hooks/useActionBtnDisabledUtils.ts

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ import { useTranslation } from 'react-i18next'
22

33
import { RUN_STATUS_BLOCKED_BY_OPEN_DOOR } from '@opentrons/api-client'
44

5+
import {
6+
useCurrentRunId,
7+
useModuleCalibrationStatus,
8+
useRunCalibrationStatus,
9+
useUnmatchedModulesForProtocol,
10+
} from '/app/resources/runs'
11+
512
import { useIsDoorOpen } from '../../../hooks'
613
import {
714
isCancellableStatus,
@@ -14,7 +21,8 @@ import type { BaseActionButtonProps } from '..'
1421
import type { DoorResult } from '../../../../../../../DoorOpenControl/useIsDoorOpen'
1522

1623
interface UseActionButtonDisabledUtilsProps extends BaseActionButtonProps {
17-
isCurrentRun: boolean
24+
robotName: string
25+
runId: string
1826
isValidRunAgain: boolean
1927
isSetupComplete: boolean
2028
isOtherRunCurrent: boolean
@@ -34,7 +42,6 @@ export function useActionBtnDisabledUtils(
3442
props: UseActionButtonDisabledUtilsProps
3543
): UseActionButtonDisabledUtilsResult {
3644
const {
37-
isCurrentRun,
3845
isSetupComplete,
3946
isOtherRunCurrent,
4047
isProtocolNotReady,
@@ -45,16 +52,29 @@ export function useActionBtnDisabledUtils(
4552
runId,
4653
isResetRunLoadingRef,
4754
isClosingCurrentRun,
55+
isCameraReadyToRun,
4856
} = props
4957

5058
const { isPlayRunActionLoading, isPauseRunActionLoading } =
5159
protocolRunControls
5260
const doorStatus = useIsDoorOpen(robotName)
5361
const isFixtureMismatch = useIsFixtureMismatch(runId, robotName)
5462
const isResetRunLoading = isResetRunLoadingRef.current
63+
const isCurrentRun = useCurrentRunId() === runId
64+
const isCalibrationComplete = !useRunCalibrationStatus(robotName, runId)
65+
.complete
66+
const isModuleCalibrationComplete = !useModuleCalibrationStatus(
67+
robotName,
68+
runId
69+
).complete
70+
const { missingModuleIds } = useUnmatchedModulesForProtocol(robotName, runId)
5571

72+
const isMissingModules = missingModuleIds.length > 0
5673
const isDisabled =
5774
(isCurrentRun && !isSetupComplete) ||
75+
isMissingModules ||
76+
isModuleCalibrationComplete ||
77+
isCalibrationComplete ||
5878
isPlayRunActionLoading ||
5979
isPauseRunActionLoading ||
6080
isResetRunLoading ||
@@ -63,16 +83,20 @@ export function useActionBtnDisabledUtils(
6383
isProtocolNotReady ||
6484
isFixtureMismatch ||
6585
isDisabledStatus(runStatus) ||
86+
!isCameraReadyToRun ||
6687
isRobotOnWrongVersionOfSoftware ||
6788
(doorStatus.isDoorOpen &&
6889
runStatus !== RUN_STATUS_BLOCKED_BY_OPEN_DOOR &&
6990
isCancellableStatus(runStatus))
7091

7192
const disabledReason = useDisabledReason({
72-
...props,
73-
doorStatus,
7493
isFixtureMismatch,
94+
doorStatus,
7595
isResetRunLoading,
96+
isMissingModules,
97+
isModuleCalibrationComplete,
98+
isCalibrationComplete,
99+
...props,
76100
})
77101

78102
return isDisabled
@@ -85,12 +109,14 @@ type UseDisabledReasonProps = UseActionButtonDisabledUtilsProps & {
85109
isFixtureMismatch: boolean
86110
isResetRunLoading: boolean
87111
isClosingCurrentRun: boolean
112+
isModuleCalibrationComplete: boolean
113+
isMissingModules: boolean
114+
isCalibrationComplete: boolean
115+
isCameraReadyToRun: boolean
88116
}
89117

90118
// The user-facing disabled explanation for why the ActionButton is disabled, if any.
91119
function useDisabledReason({
92-
isCurrentRun,
93-
isSetupComplete,
94120
isFixtureMismatch,
95121
isValidRunAgain,
96122
isOtherRunCurrent,
@@ -99,22 +125,30 @@ function useDisabledReason({
99125
runStatus,
100126
isResetRunLoading,
101127
isClosingCurrentRun,
128+
isMissingModules,
129+
isModuleCalibrationComplete,
130+
isCalibrationComplete,
102131
isCameraReadyToRun,
103132
}: UseDisabledReasonProps): string | null {
104133
const { t } = useTranslation(['run_details', 'shared'])
105-
106-
if (!isCameraReadyToRun) {
134+
if (isRobotOnWrongVersionOfSoftware) {
135+
return t('shared:a_software_update_is_available')
136+
} else if (isClosingCurrentRun) {
137+
return t('shared:robot_is_busy')
138+
} else if (!isCameraReadyToRun) {
107139
return t('enable_camera')
108-
} else if (
109-
isCurrentRun &&
110-
(!isSetupComplete || isFixtureMismatch) &&
111-
!isValidRunAgain
112-
) {
113-
return t('setup_incomplete')
140+
} else if (isFixtureMismatch) {
141+
return t('fixture_mismatch')
142+
} else if (!isValidRunAgain) {
143+
return t('run_again_disabled')
114144
} else if (isOtherRunCurrent && !isResetRunLoading) {
115145
return t('shared:robot_is_busy')
116-
} else if (isRobotOnWrongVersionOfSoftware) {
117-
return t('shared:a_software_update_is_available')
146+
} else if (!isCalibrationComplete) {
147+
return t('instrument_calibration_incomplete')
148+
} else if (isMissingModules) {
149+
return t('modules_missing')
150+
} else if (!isModuleCalibrationComplete) {
151+
return t('module_calibration_incomplete')
118152
} else if (
119153
doorStatus.isDoorOpen &&
120154
doorStatus.moduleDoorLocation !== null &&
@@ -123,8 +157,6 @@ function useDisabledReason({
123157
return t('close_stacker_door')
124158
} else if (doorStatus.isDoorOpen && isStartRunStatus(runStatus)) {
125159
return t('close_door')
126-
} else if (isClosingCurrentRun) {
127-
return t('shared:robot_is_busy')
128160
} else {
129161
return null
130162
}

app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/ActionButton/hooks/useActionButtonProperties.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { useAddCameraSettingsToRunMutation } from '@opentrons/react-api-client'
1111

1212
import { useIsHeaterShakerInProtocol } from '/app/organisms/ModuleCard/hooks'
13+
import { useToaster } from '/app/organisms/ToasterOven'
1314
import { useTrackProtocolRunEvent } from '/app/redux-resources/analytics'
1415
import {
1516
SOURCE_RUN_RECORD,
@@ -32,6 +33,7 @@ import {
3233
isRunAgainStatus,
3334
isStartRunStatus,
3435
} from '../../../utils'
36+
import { useActionBtnDisabledUtils } from './useActionBtnDisabledUtils'
3537

3638
import type { IconName } from '@opentrons/components'
3739
import type { StepKey } from '/app/redux/protocol-runs'
@@ -48,8 +50,9 @@ interface UseButtonPropertiesProps extends BaseActionButtonProps {
4850
isValidRunAgain: boolean
4951
isOtherRunCurrent: boolean
5052
isRobotOnWrongVersionOfSoftware: boolean
51-
isClosingCurrentRun: boolean
5253
areCameraPreferencesConfirmed: boolean
54+
isClosingCurrentRun: boolean
55+
isCameraReadyToRun: boolean
5356
}
5457

5558
// Returns ActionButton properties.
@@ -59,8 +62,12 @@ export function useActionButtonProperties({
5962
robotName,
6063
runId,
6164
currentRunId,
65+
isOtherRunCurrent,
66+
isRobotOnWrongVersionOfSoftware,
6267
confirmAttachment,
6368
confirmMissingSteps,
69+
makeHandleJumpToStep,
70+
runRecord,
6471
robotAnalyticsData,
6572
robotSerialNumber,
6673
protocolRunControls,
@@ -69,6 +76,9 @@ export function useActionButtonProperties({
6976
isResetRunLoadingRef,
7077
isClosingCurrentRun,
7178
areCameraPreferencesConfirmed,
79+
isValidRunAgain,
80+
protocolRunHeaderRef,
81+
isCameraReadyToRun,
7282
}: UseButtonPropertiesProps): {
7383
buttonText: string
7484
handleButtonClick: () => void
@@ -116,6 +126,27 @@ export function useActionButtonProperties({
116126
recoveryCaptureEnabled: recoveryEnabled,
117127
})
118128
}
129+
const isSetupComplete = !missingSetupSteps || missingSetupSteps.length === 0
130+
const { makeSnackbar } = useToaster()
131+
const { isDisabled, disabledReason } = useActionBtnDisabledUtils({
132+
robotName,
133+
runId,
134+
isValidRunAgain,
135+
isSetupComplete,
136+
isOtherRunCurrent,
137+
isProtocolNotReady,
138+
isRobotOnWrongVersionOfSoftware,
139+
isClosingCurrentRun,
140+
makeHandleJumpToStep,
141+
runRecord,
142+
runStatus,
143+
isResetRunLoadingRef,
144+
protocolRunHeaderRef,
145+
attachedModules,
146+
protocolRunControls,
147+
runHeaderModalContainerUtils,
148+
isCameraReadyToRun,
149+
})
119150

120151
if (isProtocolNotReady) {
121152
buttonIconName = 'ot-spinner'
@@ -138,6 +169,10 @@ export function useActionButtonProperties({
138169
buttonText =
139170
runStatus === RUN_STATUS_IDLE ? t('start_run') : t('resume_run')
140171
handleButtonClick = () => {
172+
if (isDisabled && disabledReason) {
173+
makeSnackbar(disabledReason)
174+
return
175+
}
141176
if (isHeaterShakerShaking && isHeaterShakerInProtocol) {
142177
runHeaderModalContainerUtils.HSRunningModalUtils.toggleModal?.()
143178
} else if (

0 commit comments

Comments
 (0)