diff --git a/app/src/assets/localization/en/device_details.json b/app/src/assets/localization/en/device_details.json index 71df492e080..31a9b06b78a 100644 --- a/app/src/assets/localization/en/device_details.json +++ b/app/src/assets/localization/en/device_details.json @@ -88,6 +88,7 @@ "missing_module": "missing {{num}} module", "module_actions_unavailable": "Module actions unavailable while protocol is running", "module_calibration_required_no_pipette_attached": "Module calibration required. Attach a pipette before running module calibration.", + "module_calibration_required_no_pipette_calibrated": "Module calibration required. Calibrate pipette before running module calibration. ", "module_calibration_required_update_pipette_FW": "Update pipette firmware before proceeding with required module calibration.", "module_calibration_required": "Module calibration required.", "module_controls": "Module Controls", diff --git a/app/src/molecules/UpdateBanner/__tests__/UpdateBanner.test.tsx b/app/src/molecules/UpdateBanner/__tests__/UpdateBanner.test.tsx index c04943ca650..9cd38ce7eca 100644 --- a/app/src/molecules/UpdateBanner/__tests__/UpdateBanner.test.tsx +++ b/app/src/molecules/UpdateBanner/__tests__/UpdateBanner.test.tsx @@ -85,6 +85,14 @@ describe('Module Update Banner', () => { const { queryByText } = render(props) expect(queryByText('Calibrate now')).not.toBeInTheDocument() }) + it('should not render a calibrate link if pipette calibration is required', () => { + props = { + ...props, + calibratePipetteRequired: true, + } + const { queryByText } = render(props) + expect(queryByText('Calibrate now')).not.toBeInTheDocument() + }) it('should not render a calibrate link if pipette firmware update is required', () => { props = { ...props, @@ -98,6 +106,7 @@ describe('Module Update Banner', () => { ...props, updateType: 'firmware', attachPipetteRequired: true, + calibratePipetteRequired: true, updatePipetteFWRequired: true, } const { queryByText } = render(props) diff --git a/app/src/molecules/UpdateBanner/index.tsx b/app/src/molecules/UpdateBanner/index.tsx index 14e3b1485de..23455d46c4e 100644 --- a/app/src/molecules/UpdateBanner/index.tsx +++ b/app/src/molecules/UpdateBanner/index.tsx @@ -23,6 +23,7 @@ interface UpdateBannerProps { serialNumber: string isTooHot?: boolean attachPipetteRequired?: boolean + calibratePipetteRequired?: boolean updatePipetteFWRequired?: boolean } @@ -33,6 +34,7 @@ export const UpdateBanner = ({ setShowBanner, handleUpdateClick, attachPipetteRequired, + calibratePipetteRequired, updatePipetteFWRequired, isTooHot, }: UpdateBannerProps): JSX.Element | null => { @@ -48,11 +50,16 @@ export const UpdateBanner = ({ closeButtonRendered = false if (attachPipetteRequired) bannerMessage = t('module_calibration_required_no_pipette_attached') + else if (calibratePipetteRequired) + bannerMessage = t('module_calibration_required_no_pipette_calibrated') else if (updatePipetteFWRequired) bannerMessage = t('module_calibration_required_update_pipette_FW') else bannerMessage = t('module_calibration_required') hyperlinkText = - !attachPipetteRequired && !updatePipetteFWRequired && !isTooHot + !attachPipetteRequired && + !updatePipetteFWRequired && + !isTooHot && + !calibratePipetteRequired ? t('calibrate_now') : '' } else { diff --git a/app/src/organisms/Devices/InstrumentsAndModules.tsx b/app/src/organisms/Devices/InstrumentsAndModules.tsx index ef6cb2a7f60..25007ca3d87 100644 --- a/app/src/organisms/Devices/InstrumentsAndModules.tsx +++ b/app/src/organisms/Devices/InstrumentsAndModules.tsx @@ -105,6 +105,8 @@ export function InstrumentsAndModules({ attachedPipettes?.left ?? null ) const attachPipetteRequired = + attachedLeftPipette == null && attachedRightPipette == null + const calibratePipetteRequired = attachedLeftPipette?.data?.calibratedOffset?.last_modified == null && attachedRightPipette?.data?.calibratedOffset?.last_modified == null const updatePipetteFWRequired = @@ -240,6 +242,7 @@ export function InstrumentsAndModules({ module={module} isLoadedInRun={false} attachPipetteRequired={attachPipetteRequired} + calibratePipetteRequired={calibratePipetteRequired} updatePipetteFWRequired={updatePipetteFWRequired} /> ))} @@ -281,6 +284,7 @@ export function InstrumentsAndModules({ module={module} isLoadedInRun={false} attachPipetteRequired={attachPipetteRequired} + calibratePipetteRequired={calibratePipetteRequired} updatePipetteFWRequired={updatePipetteFWRequired} /> ))} diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolRunModuleControls.tsx b/app/src/organisms/Devices/ProtocolRun/ProtocolRunModuleControls.tsx index f8dcee3d93b..107db017315 100644 --- a/app/src/organisms/Devices/ProtocolRun/ProtocolRunModuleControls.tsx +++ b/app/src/organisms/Devices/ProtocolRun/ProtocolRunModuleControls.tsx @@ -15,6 +15,7 @@ import type { BadPipette, PipetteData } from '@opentrons/api-client' interface PipetteStatus { attachPipetteRequired: boolean + calibratePipetteRequired: boolean updatePipetteFWRequired: boolean } @@ -51,9 +52,16 @@ const usePipetteIsReady = (): PipetteStatus => { const attachPipetteRequired = attachedLeftPipette == null && attachedRightPipette == null + const calibratePipetteRequired = + attachedLeftPipette?.data.calibratedOffset?.last_modified == null && + attachedRightPipette?.data.calibratedOffset?.last_modified == null const updatePipetteFWRequired = leftPipetteRequiresFWUpdate != null || rightPipetteFWRequired != null - return { attachPipetteRequired, updatePipetteFWRequired } + return { + attachPipetteRequired, + calibratePipetteRequired, + updatePipetteFWRequired, + } } interface ProtocolRunModuleControlsProps { @@ -67,7 +75,11 @@ export const ProtocolRunModuleControls = ({ }: ProtocolRunModuleControlsProps): JSX.Element => { const { t } = useTranslation('protocol_details') - const { attachPipetteRequired, updatePipetteFWRequired } = usePipetteIsReady() + const { + attachPipetteRequired, + calibratePipetteRequired, + updatePipetteFWRequired, + } = usePipetteIsReady() const moduleRenderInfoForProtocolById = useModuleRenderInfoForProtocolById( runId @@ -115,6 +127,7 @@ export const ProtocolRunModuleControls = ({ slotName={module.slotName} isLoadedInRun={true} attachPipetteRequired={attachPipetteRequired} + calibratePipetteRequired={calibratePipetteRequired} updatePipetteFWRequired={updatePipetteFWRequired} /> ) : null @@ -135,6 +148,7 @@ export const ProtocolRunModuleControls = ({ slotName={module.slotName} isLoadedInRun={true} attachPipetteRequired={attachPipetteRequired} + calibratePipetteRequired={calibratePipetteRequired} updatePipetteFWRequired={updatePipetteFWRequired} /> ) : null diff --git a/app/src/organisms/ModuleCard/__tests__/ModuleCard.test.tsx b/app/src/organisms/ModuleCard/__tests__/ModuleCard.test.tsx index 599732ad763..31e0f92cd31 100644 --- a/app/src/organisms/ModuleCard/__tests__/ModuleCard.test.tsx +++ b/app/src/organisms/ModuleCard/__tests__/ModuleCard.test.tsx @@ -227,6 +227,7 @@ describe('ModuleCard', () => { robotName: mockRobot.name, isLoadedInRun: false, attachPipetteRequired: false, + calibratePipetteRequired: false, updatePipetteFWRequired: false, } diff --git a/app/src/organisms/ModuleCard/index.tsx b/app/src/organisms/ModuleCard/index.tsx index eb42bf67f39..2d76c99c97f 100644 --- a/app/src/organisms/ModuleCard/index.tsx +++ b/app/src/organisms/ModuleCard/index.tsx @@ -79,6 +79,7 @@ interface ModuleCardProps { robotName: string isLoadedInRun: boolean attachPipetteRequired: boolean + calibratePipetteRequired: boolean updatePipetteFWRequired: boolean runId?: string slotName?: string @@ -93,6 +94,7 @@ export const ModuleCard = (props: ModuleCardProps): JSX.Element | null => { runId, slotName, attachPipetteRequired, + calibratePipetteRequired, updatePipetteFWRequired, } = props const dispatch = useDispatch() @@ -125,7 +127,9 @@ export const ModuleCard = (props: ModuleCardProps): JSX.Element | null => { }) const requireModuleCalibration = module.moduleOffset?.last_modified == null const isPipetteReady = - (!attachPipetteRequired ?? false) && (!updatePipetteFWRequired ?? false) + (!attachPipetteRequired ?? false) && + (!calibratePipetteRequired ?? false) && + (!updatePipetteFWRequired ?? false) const latestRequestId = last(requestIds) const latestRequest = useSelector(state => latestRequestId ? getRequestById(state, latestRequestId) : null @@ -303,6 +307,7 @@ export const ModuleCard = (props: ModuleCardProps): JSX.Element | null => { /> )} {attachPipetteRequired != null && + calibratePipetteRequired != null && updatePipetteFWRequired != null && requireModuleCalibration && !isPending ? ( @@ -313,6 +318,7 @@ export const ModuleCard = (props: ModuleCardProps): JSX.Element | null => { setShowBanner={() => null} handleUpdateClick={handleCalibrateClick} attachPipetteRequired={attachPipetteRequired} + calibratePipetteRequired={calibratePipetteRequired} updatePipetteFWRequired={updatePipetteFWRequired} isTooHot={isTooHot} />