Skip to content
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
5 changes: 5 additions & 0 deletions app/src/assets/localization/en/run_details.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"error_type": "Error: {{errorType}}",
"failed_step": "Failed step",
"final_step": "Final Step",
"fixture_mismatch": "Deck configuration does not match protocol requirements",
"ignore_stored_data": "Ignore stored data",
"image_at_step_at_timestamp": "Image at {{step}} at {{timestamp}}",
"image_capture": "Image capture",
Expand All @@ -77,6 +78,7 @@
"image_loading": "Image loading",
"images": "Images",
"images_available_download": "Image files associated with the run are available on the robot’s Recent Runs screen and Camera tab.",
"instrument_calibration_incomplete": "Instrument calibration is incomplete",
"labware": "labware",
"labware_offset_data": "labware offset data",
"left": "Left",
Expand All @@ -91,7 +93,9 @@
"loading_data": "Loading data...",
"loading_protocol": "Loading Protocol",
"location": "location",
"module_calibration_incomplete": "Module calibration is incomplete",
"module_controls": "Module Controls",
"modules_missing": "Modules are missing from deck hardware",
"module_slot_number": "Slot {{slot_number}}",
"move_labware": "Move Labware",
"na": "N/A",
Expand Down Expand Up @@ -141,6 +145,7 @@
"robot_was_recalibrated": "This robot was recalibrated after this Labware Offset data was stored.",
"run": "Run",
"run_again": "Run again",
"run_again_disabled": "Run again is disabled for this protocol",
"run_canceled": "Run canceled.",
"run_canceled_splash": "Run canceled",
"run_canceled_with_errors": "Run canceled with errors.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import { useTranslation } from 'react-i18next'

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

import {
useCurrentRunId,
useModuleCalibrationStatus,
useRunCalibrationStatus,
useUnmatchedModulesForProtocol,
} from '/app/resources/runs'

import { useIsDoorOpen } from '../../../hooks'
import {
isCancellableStatus,
Expand All @@ -14,7 +21,8 @@ import type { BaseActionButtonProps } from '..'
import type { DoorResult } from '../../../../../../../DoorOpenControl/useIsDoorOpen'

interface UseActionButtonDisabledUtilsProps extends BaseActionButtonProps {
isCurrentRun: boolean
robotName: string
runId: string
isValidRunAgain: boolean
isSetupComplete: boolean
isOtherRunCurrent: boolean
Expand All @@ -34,7 +42,6 @@ export function useActionBtnDisabledUtils(
props: UseActionButtonDisabledUtilsProps
): UseActionButtonDisabledUtilsResult {
const {
isCurrentRun,
isSetupComplete,
isOtherRunCurrent,
isProtocolNotReady,
Expand All @@ -45,16 +52,29 @@ export function useActionBtnDisabledUtils(
runId,
isResetRunLoadingRef,
isClosingCurrentRun,
isCameraReadyToRun,
} = props

const { isPlayRunActionLoading, isPauseRunActionLoading } =
protocolRunControls
const doorStatus = useIsDoorOpen(robotName)
const isFixtureMismatch = useIsFixtureMismatch(runId, robotName)
const isResetRunLoading = isResetRunLoadingRef.current
const isCurrentRun = useCurrentRunId() === runId
const isCalibrationComplete = !useRunCalibrationStatus(robotName, runId)
.complete
const isModuleCalibrationComplete = !useModuleCalibrationStatus(
robotName,
runId
).complete
const { missingModuleIds } = useUnmatchedModulesForProtocol(robotName, runId)

const isMissingModules = missingModuleIds.length > 0
const isDisabled =
(isCurrentRun && !isSetupComplete) ||
isMissingModules ||
isModuleCalibrationComplete ||
isCalibrationComplete ||
isPlayRunActionLoading ||
isPauseRunActionLoading ||
isResetRunLoading ||
Expand All @@ -63,16 +83,20 @@ export function useActionBtnDisabledUtils(
isProtocolNotReady ||
isFixtureMismatch ||
isDisabledStatus(runStatus) ||
!isCameraReadyToRun ||
isRobotOnWrongVersionOfSoftware ||
(doorStatus.isDoorOpen &&
runStatus !== RUN_STATUS_BLOCKED_BY_OPEN_DOOR &&
isCancellableStatus(runStatus))

const disabledReason = useDisabledReason({
...props,
doorStatus,
isFixtureMismatch,
doorStatus,
isResetRunLoading,
isMissingModules,
isModuleCalibrationComplete,
isCalibrationComplete,
...props,
})

return isDisabled
Expand All @@ -85,12 +109,14 @@ type UseDisabledReasonProps = UseActionButtonDisabledUtilsProps & {
isFixtureMismatch: boolean
isResetRunLoading: boolean
isClosingCurrentRun: boolean
isModuleCalibrationComplete: boolean
isMissingModules: boolean
isCalibrationComplete: boolean
isCameraReadyToRun: boolean
}

// The user-facing disabled explanation for why the ActionButton is disabled, if any.
function useDisabledReason({
isCurrentRun,
isSetupComplete,
isFixtureMismatch,
isValidRunAgain,
isOtherRunCurrent,
Expand All @@ -99,22 +125,30 @@ function useDisabledReason({
runStatus,
isResetRunLoading,
isClosingCurrentRun,
isMissingModules,
isModuleCalibrationComplete,
isCalibrationComplete,
isCameraReadyToRun,
}: UseDisabledReasonProps): string | null {
const { t } = useTranslation(['run_details', 'shared'])

if (!isCameraReadyToRun) {
if (isRobotOnWrongVersionOfSoftware) {
return t('shared:a_software_update_is_available')
} else if (isClosingCurrentRun) {
return t('shared:robot_is_busy')
} else if (!isCameraReadyToRun) {
return t('enable_camera')
} else if (
isCurrentRun &&
(!isSetupComplete || isFixtureMismatch) &&
!isValidRunAgain
) {
return t('setup_incomplete')
} else if (isFixtureMismatch) {
return t('fixture_mismatch')
} else if (!isValidRunAgain) {
return t('run_again_disabled')
} else if (isOtherRunCurrent && !isResetRunLoading) {
return t('shared:robot_is_busy')
} else if (isRobotOnWrongVersionOfSoftware) {
return t('shared:a_software_update_is_available')
} else if (!isCalibrationComplete) {
return t('instrument_calibration_incomplete')
} else if (isMissingModules) {
return t('modules_missing')
} else if (!isModuleCalibrationComplete) {
return t('module_calibration_incomplete')
} else if (
doorStatus.isDoorOpen &&
doorStatus.moduleDoorLocation !== null &&
Expand All @@ -123,8 +157,6 @@ function useDisabledReason({
return t('close_stacker_door')
} else if (doorStatus.isDoorOpen && isStartRunStatus(runStatus)) {
return t('close_door')
} else if (isClosingCurrentRun) {
return t('shared:robot_is_busy')
} else {
return null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { useAddCameraSettingsToRunMutation } from '@opentrons/react-api-client'

import { useIsHeaterShakerInProtocol } from '/app/organisms/ModuleCard/hooks'
import { useToaster } from '/app/organisms/ToasterOven'
import { useTrackProtocolRunEvent } from '/app/redux-resources/analytics'
import {
SOURCE_RUN_RECORD,
Expand All @@ -32,6 +33,7 @@ import {
isRunAgainStatus,
isStartRunStatus,
} from '../../../utils'
import { useActionBtnDisabledUtils } from './useActionBtnDisabledUtils'

import type { IconName } from '@opentrons/components'
import type { StepKey } from '/app/redux/protocol-runs'
Expand All @@ -48,8 +50,9 @@ interface UseButtonPropertiesProps extends BaseActionButtonProps {
isValidRunAgain: boolean
isOtherRunCurrent: boolean
isRobotOnWrongVersionOfSoftware: boolean
isClosingCurrentRun: boolean
areCameraPreferencesConfirmed: boolean
isClosingCurrentRun: boolean
isCameraReadyToRun: boolean
}

// Returns ActionButton properties.
Expand All @@ -59,8 +62,12 @@ export function useActionButtonProperties({
robotName,
runId,
currentRunId,
isOtherRunCurrent,
isRobotOnWrongVersionOfSoftware,
confirmAttachment,
confirmMissingSteps,
makeHandleJumpToStep,
runRecord,
robotAnalyticsData,
robotSerialNumber,
protocolRunControls,
Expand All @@ -69,6 +76,9 @@ export function useActionButtonProperties({
isResetRunLoadingRef,
isClosingCurrentRun,
areCameraPreferencesConfirmed,
isValidRunAgain,
protocolRunHeaderRef,
isCameraReadyToRun,
}: UseButtonPropertiesProps): {
buttonText: string
handleButtonClick: () => void
Expand Down Expand Up @@ -116,6 +126,27 @@ export function useActionButtonProperties({
recoveryCaptureEnabled: recoveryEnabled,
})
}
const isSetupComplete = !missingSetupSteps || missingSetupSteps.length === 0
const { makeSnackbar } = useToaster()
const { isDisabled, disabledReason } = useActionBtnDisabledUtils({
robotName,
runId,
isValidRunAgain,
isSetupComplete,
isOtherRunCurrent,
isProtocolNotReady,
isRobotOnWrongVersionOfSoftware,
isClosingCurrentRun,
makeHandleJumpToStep,
runRecord,
runStatus,
isResetRunLoadingRef,
protocolRunHeaderRef,
attachedModules,
protocolRunControls,
runHeaderModalContainerUtils,
isCameraReadyToRun,
})

if (isProtocolNotReady) {
buttonIconName = 'ot-spinner'
Expand All @@ -138,6 +169,10 @@ export function useActionButtonProperties({
buttonText =
runStatus === RUN_STATUS_IDLE ? t('start_run') : t('resume_run')
handleButtonClick = () => {
if (isDisabled && disabledReason) {
makeSnackbar(disabledReason)
return
}
if (isHeaterShakerShaking && isHeaterShakerInProtocol) {
runHeaderModalContainerUtils.HSRunningModalUtils.toggleModal?.()
} else if (
Expand Down
Loading
Loading