From 68df7923af9880f78c9c09b507fe3fa383d6e90b Mon Sep 17 00:00:00 2001 From: Jamey Huffnagle Date: Wed, 26 Mar 2025 10:59:06 -0400 Subject: [PATCH 01/10] feat(app): skip labware LPC on the OT-2 --- .../en/labware_position_check.json | 1 + .../LegacyLabwarePositionCheck/CheckItem.tsx | 1 + .../PrepareSpace.tsx | 24 +++++++++++++++---- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/app/src/assets/localization/en/labware_position_check.json b/app/src/assets/localization/en/labware_position_check.json index 93879150b3bd..5ee876a5e003 100644 --- a/app/src/assets/localization/en/labware_position_check.json +++ b/app/src/assets/localization/en/labware_position_check.json @@ -162,6 +162,7 @@ "secondary_pipette_tipracks_section": "Check tip racks with {{secondary_mount}} Pipette", "see_how_offsets_work": "See how labware offsets work", "select_labware_to_view_data": "Select a labware to view its stored offset data.", + "skip": "Skip", "slot": "Slot {{slotName}}", "slot_applied_location_offset_updated": "{{slot}} applied location offset updated", "slot_in_module_applied_location_offset_updated": "{{slot}} in {{module}} applied location offset updated", diff --git a/app/src/organisms/LegacyLabwarePositionCheck/CheckItem.tsx b/app/src/organisms/LegacyLabwarePositionCheck/CheckItem.tsx index fe5c8f93ef4c..d8d23ed08b7e 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/CheckItem.tsx +++ b/app/src/organisms/LegacyLabwarePositionCheck/CheckItem.tsx @@ -506,6 +506,7 @@ export const CheckItem = (props: CheckItemProps): JSX.Element | null => { labwareDef={labwareDef} confirmPlacement={handleConfirmPlacement} robotType={robotType} + onSkip={proceed} /> )} diff --git a/app/src/organisms/LegacyLabwarePositionCheck/PrepareSpace.tsx b/app/src/organisms/LegacyLabwarePositionCheck/PrepareSpace.tsx index f77973cbc1fc..55dc3d2dbf63 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/PrepareSpace.tsx +++ b/app/src/organisms/LegacyLabwarePositionCheck/PrepareSpace.tsx @@ -14,6 +14,7 @@ import { PrimaryButton, BaseDeck, ALIGN_FLEX_START, + SecondaryButton, } from '@opentrons/components' import { THERMOCYCLER_MODULE_TYPE, getModuleType } from '@opentrons/shared-data' @@ -61,13 +62,23 @@ interface PrepareSpaceProps extends Omit { labwareDef: LabwareDefinition2 protocolData: CompletedProtocolAnalysis confirmPlacement: () => void + onSkip: () => void header: ReactNode body: ReactNode robotType: RobotType } export const PrepareSpace = (props: PrepareSpaceProps): JSX.Element | null => { const { i18n, t } = useTranslation(['labware_position_check', 'shared']) - const { location, labwareDef, protocolData, header, body, robotType } = props + const { + location, + labwareDef, + protocolData, + header, + body, + robotType, + section, + onSkip, + } = props const isOnDevice = useSelector(getIsOnDevice) const deckConfig = useNotifyDeckConfigurationQuery().data ?? [] @@ -131,9 +142,14 @@ export const PrepareSpace = (props: PrepareSpaceProps): JSX.Element | null => { ) : ( - - {i18n.format(t('shared:confirm_placement'), 'capitalize')} - + + {section === 'CHECK_LABWARE' && ( + {t('skip')} + )} + + {i18n.format(t('shared:confirm_placement'), 'capitalize')} + + )} From ab60a546112f99669fc2a2adb4777d90df8877bf Mon Sep 17 00:00:00 2001 From: Jamey Huffnagle Date: Wed, 26 Mar 2025 14:32:15 -0400 Subject: [PATCH 02/10] refactor(app): oT-2 LPC: Save offsets after each step --- .../LegacyLabwarePositionCheck/CheckItem.tsx | 37 +++++- .../LegacyLabwarePositionCheck/JogToWell.tsx | 31 ++++- .../LegacyLabwarePositionCheckComponent.tsx | 112 ++++++++++++++---- .../LegacyLabwarePositionCheck/PickUpTip.tsx | 46 +++++-- .../ResultsSummary.tsx | 72 ++--------- .../LegacyLabwarePositionCheck/ReturnTip.tsx | 3 + .../__tests__/CheckItem.test.tsx | 7 ++ .../__tests__/PickUpTip.test.tsx | 16 +++ .../__tests__/ResultsSummary.test.tsx | 28 ++--- .../__tests__/ReturnTip.test.tsx | 1 + 10 files changed, 236 insertions(+), 117 deletions(-) diff --git a/app/src/organisms/LegacyLabwarePositionCheck/CheckItem.tsx b/app/src/organisms/LegacyLabwarePositionCheck/CheckItem.tsx index d8d23ed08b7e..7faa04696364 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/CheckItem.tsx +++ b/app/src/organisms/LegacyLabwarePositionCheck/CheckItem.tsx @@ -21,6 +21,8 @@ import { HEATERSHAKER_MODULE_TYPE, IDENTITY_VECTOR, THERMOCYCLER_MODULE_TYPE, + getVectorDifference, + getVectorSum, } from '@opentrons/shared-data' import { useSelector } from 'react-redux' import { getLabwareDef } from './utils/labware' @@ -30,13 +32,17 @@ import { getIsOnDevice } from '/app/redux/config' import { getDisplayLocation } from './utils/getDisplayLocation' import type { Dispatch } from 'react' -import type { LabwareOffset } from '@opentrons/api-client' +import type { + LabwareOffset, + LegacyLabwareOffsetLocation, +} from '@opentrons/api-client' import type { CompletedProtocolAnalysis, CreateCommand, LabwareLocation, MoveLabwareCreateCommand, RobotType, + Coordinates, } from '@opentrons/shared-data' import type { useChainRunCommands } from '/app/resources/runs' import type { @@ -55,6 +61,13 @@ interface CheckItemProps extends Omit { proceed: () => void chainRunCommands: ReturnType['chainRunCommands'] setFatalError: (errorMessage: string) => void + isApplyingOffsets: boolean + calculateAndApplyOffset: ( + initialPosition: Coordinates | null, + finalPosition: Coordinates | null, + labwareId: string, + location: LegacyLabwareOffsetLocation + ) => Promise registerPosition: Dispatch workingOffsets: WorkingOffset[] existingOffsets: LabwareOffset[] @@ -63,6 +76,7 @@ interface CheckItemProps extends Omit { robotType: RobotType shouldUseMetalProbe: boolean } + export const CheckItem = (props: CheckItemProps): JSX.Element | null => { const { labwareId, @@ -80,7 +94,9 @@ export const CheckItem = (props: CheckItemProps): JSX.Element | null => { existingOffsets, setFatalError, robotType, + isApplyingOffsets, shouldUseMetalProbe, + calculateAndApplyOffset, } = props const { t, i18n } = useTranslation(['labware_position_check', 'shared']) const isOnDevice = useSelector(getIsOnDevice) @@ -355,7 +371,7 @@ export const CheckItem = (props: CheckItemProps): JSX.Element | null => { }, ] - const handleConfirmPosition = (): void => { + const handleConfirmPositionAndApply = (): void => { const heaterShakerPrepCommands: CreateCommand[] = moduleId != null && moduleType != null && @@ -403,17 +419,29 @@ export const CheckItem = (props: CheckItemProps): JSX.Element | null => { location, position, }) - proceed() + return calculateAndApplyOffset( + initialPosition ?? null, + position, + labwareId, + location + ) } else { setFatalError('CheckItem failed to save final position with message') + return Promise.reject( + new Error('CheckItem failed to save final position with message') + ) } }) + .then(() => { + proceed() + }) .catch((e: Error) => { setFatalError( `CheckItem failed to move from final position with message: ${e.message}` ) }) } + const handleGoBack = (): void => { chainRunCommands( [ @@ -479,7 +507,8 @@ export const CheckItem = (props: CheckItemProps): JSX.Element | null => { } labwareDef={labwareDef} pipetteName={pipetteName} - handleConfirmPosition={handleConfirmPosition} + handleConfirmPositionAndApply={handleConfirmPositionAndApply} + isApplyingOffsets={isApplyingOffsets} handleGoBack={handleGoBack} handleJog={handleJog} initialPosition={initialPosition} diff --git a/app/src/organisms/LegacyLabwarePositionCheck/JogToWell.tsx b/app/src/organisms/LegacyLabwarePositionCheck/JogToWell.tsx index 122dc1d91a26..7198c3a251c5 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/JogToWell.tsx +++ b/app/src/organisms/LegacyLabwarePositionCheck/JogToWell.tsx @@ -9,6 +9,7 @@ import { COLORS, DIRECTION_COLUMN, Flex, + Icon, JUSTIFY_CENTER, JUSTIFY_SPACE_BETWEEN, LabwareRender, @@ -53,7 +54,8 @@ const LPC_HELP_LINK_URL = 'https://support.opentrons.com/s/article/How-Labware-Offsets-work-on-the-OT-2' interface JogToWellProps { - handleConfirmPosition: () => void + handleConfirmPositionAndApply: () => void + isApplyingOffsets: boolean handleGoBack: () => void handleJog: Jog pipetteName: PipetteName @@ -71,7 +73,8 @@ export const JogToWell = (props: JogToWellProps): JSX.Element | null => { body, pipetteName, labwareDef, - handleConfirmPosition, + handleConfirmPositionAndApply, + isApplyingOffsets, handleGoBack, handleJog, initialPosition, @@ -198,7 +201,7 @@ export const JogToWell = (props: JogToWellProps): JSX.Element | null => { /> {showFullJogControls @@ -259,11 +262,27 @@ export const JogToWell = (props: JogToWellProps): JSX.Element | null => { > - + {t('shared:go_back')} - - {t('shared:confirm_position')} + + + {isApplyingOffsets ? ( + + ) : null} + {t('shared:confirm_position')} + diff --git a/app/src/organisms/LegacyLabwarePositionCheck/LegacyLabwarePositionCheckComponent.tsx b/app/src/organisms/LegacyLabwarePositionCheck/LegacyLabwarePositionCheckComponent.tsx index e6fb5bf47b49..0ec60aab6527 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/LegacyLabwarePositionCheckComponent.tsx +++ b/app/src/organisms/LegacyLabwarePositionCheck/LegacyLabwarePositionCheckComponent.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useReducer } from 'react' +import { useState, useEffect, useReducer, useMemo } from 'react' import { createPortal } from 'react-dom' import isEqual from 'lodash/isEqual' import { useSelector } from 'react-redux' @@ -9,7 +9,13 @@ import { useAddLabwareOffsetToRunMutation, useCreateMaintenanceCommandMutation, } from '@opentrons/react-api-client' -import { FIXED_TRASH_ID, FLEX_ROBOT_TYPE } from '@opentrons/shared-data' +import { + FIXED_TRASH_ID, + FLEX_ROBOT_TYPE, + getVectorDifference, + getVectorSum, + IDENTITY_VECTOR, +} from '@opentrons/shared-data' import { getTopPortalEl } from '/app/App/portal' // import { useTrackEvent } from '/app/redux/analytics' @@ -30,6 +36,7 @@ import { useNotifyCurrentMaintenanceRun, } from '/app/resources/maintenance_runs' import { getLabwarePositionCheckSteps } from './getLabwarePositionCheckSteps' +import { getCurrentOffsetForLabwareInLocation } from '/app/transformations/analysis' import type { CompletedProtocolAnalysis, @@ -42,6 +49,7 @@ import type { LegacyLabwareOffsetCreateData, LabwareOffset, CommandData, + LegacyLabwareOffsetLocation, } from '@opentrons/api-client' import type { Axis, Sign, StepSize } from '/app/molecules/JogControls/types' import type { RegisterPositionAction, WorkingOffset } from './types' @@ -184,6 +192,78 @@ export const LegacyLabwarePositionCheckComponent = ( }, { workingOffsets: [], tipPickUpOffset: null } ) + + const allWorkingOffsets = useMemo(() => { + return workingOffsets.reduce( + (acc, { initialPosition, finalPosition, labwareId, location }) => { + const definitionUri = + protocolData?.labware.find(l => l.id === labwareId)?.definitionUri ?? + null + if ( + finalPosition == null || + initialPosition == null || + definitionUri == null + ) { + return acc + } + + const existingOffset = + getCurrentOffsetForLabwareInLocation( + existingOffsets, + definitionUri, + location + )?.vector ?? IDENTITY_VECTOR + const vector = getVectorSum( + existingOffset, + getVectorDifference(finalPosition, initialPosition) + ) + return [...acc, { definitionUri, location, vector }] + }, + [] + ) + }, [workingOffsets, protocolData, existingOffsets]) + + const { createLabwareOffset } = useAddLabwareOffsetToRunMutation() + const calculateAndApplyOffset = ( + initialPosition: Coordinates | null, + finalPosition: Coordinates | null, + labwareId: string, + location: LegacyLabwareOffsetLocation + ): Promise => { + if (initialPosition != null && finalPosition != null) { + const definitionUri = protocolData?.labware.find(l => l.id === labwareId) + ?.definitionUri + + if (definitionUri) { + const existingOffset = + getCurrentOffsetForLabwareInLocation( + existingOffsets, + definitionUri, + location + )?.vector ?? IDENTITY_VECTOR + + const vectorDiff = getVectorDifference(finalPosition, initialPosition) + const newOffset = getVectorSum(existingOffset, vectorDiff) + + setIsApplyingOffsets(true) + return createLabwareOffset({ + runId, + data: { definitionUri, location, vector: newOffset }, + }) + .then(() => { + setIsApplyingOffsets(false) + }) + .catch((e: Error) => { + setFatalError(`error applying labware offsets: ${e.message}`) + setIsApplyingOffsets(false) + return Promise.reject(e) + }) + } + } + + return Promise.resolve() + } + const [isExiting, setIsExiting] = useState(false) const { createMaintenanceCommand: createSilentCommand, @@ -193,7 +273,6 @@ export const LegacyLabwarePositionCheckComponent = ( isCommandMutationLoading: isCommandChainLoading, } = useChainMaintenanceCommands() - const { createLabwareOffset } = useAddLabwareOffsetToRunMutation() const [currentStepIndex, setCurrentStepIndex] = useState(0) const handleCleanUpAndClose = (): void => { setIsExiting(true) @@ -259,6 +338,7 @@ export const LegacyLabwarePositionCheckComponent = ( ) const totalStepCount = LPCSteps.length - 1 const currentStep = LPCSteps?.[currentStepIndex] + if (currentStep == null) return null const protocolHasModules = protocolData.modules.length > 0 @@ -297,6 +377,7 @@ export const LegacyLabwarePositionCheckComponent = ( continuePastCommandFailure: boolean ): Promise => chainRunCommands(maintenanceRunId, commands, continuePastCommandFailure) + const movementStepProps = { proceed, protocolData, @@ -310,21 +391,6 @@ export const LegacyLabwarePositionCheckComponent = ( robotType, } - const handleApplyOffsets = ( - offsets: LegacyLabwareOffsetCreateData[] - ): void => { - setIsApplyingOffsets(true) - Promise.all(offsets.map(data => createLabwareOffset({ runId, data }))) - .then(() => { - onCloseClick() - setIsApplyingOffsets(false) - }) - .catch((e: Error) => { - setFatalError(`error applying labware offsets: ${e.message}`) - setIsApplyingOffsets(false) - }) - } - let modalContent: JSX.Element =
UNASSIGNED STEP
if (isExiting) { modalContent = ( @@ -365,6 +431,8 @@ export const LegacyLabwarePositionCheckComponent = ( {...currentStep} {...movementStepProps} shouldUseMetalProbe={shouldUseMetalProbe} + isApplyingOffsets={isApplyingOffsets} + calculateAndApplyOffset={calculateAndApplyOffset} /> ) } else if (currentStep.section === 'ATTACH_PROBE') { @@ -384,6 +452,9 @@ export const LegacyLabwarePositionCheckComponent = ( {...movementStepProps} protocolHasModules={protocolHasModules} currentStepIndex={currentStepIndex} + isApplyingOffsets={isApplyingOffsets} + onSkip={proceed} + calculateAndApplyOffset={calculateAndApplyOffset} /> ) } else if (currentStep.section === 'RETURN_TIP') { @@ -392,6 +463,7 @@ export const LegacyLabwarePositionCheckComponent = ( {...currentStep} {...movementStepProps} {...{ tipPickUpOffset }} + onSkip={proceed} /> ) } else if (currentStep.section === 'RESULTS_SUMMARY') { @@ -399,11 +471,11 @@ export const LegacyLabwarePositionCheckComponent = ( diff --git a/app/src/organisms/LegacyLabwarePositionCheck/PickUpTip.tsx b/app/src/organisms/LegacyLabwarePositionCheck/PickUpTip.tsx index d8312fbd51aa..b62d4a76a5a5 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/PickUpTip.tsx +++ b/app/src/organisms/LegacyLabwarePositionCheck/PickUpTip.tsx @@ -33,6 +33,7 @@ import type { CreateCommand, MoveLabwareCreateCommand, RobotType, + Coordinates, } from '@opentrons/shared-data' import type { useChainRunCommands } from '/app/resources/runs' import type { Jog } from '/app/molecules/JogControls/types' @@ -41,7 +42,10 @@ import type { RegisterPositionAction, WorkingOffset, } from './types' -import type { LabwareOffset } from '@opentrons/api-client' +import type { + LabwareOffset, + LegacyLabwareOffsetLocation, +} from '@opentrons/api-client' import type { TFunction } from 'i18next' interface PickUpTipProps extends PickUpTipStep { @@ -50,6 +54,14 @@ interface PickUpTipProps extends PickUpTipStep { registerPosition: Dispatch chainRunCommands: ReturnType['chainRunCommands'] setFatalError: (errorMessage: string) => void + calculateAndApplyOffset: ( + initialPosition: Coordinates | null, + finalPosition: Coordinates | null, + labwareId: string, + location: LegacyLabwareOffsetLocation + ) => Promise + onSkip: () => void + isApplyingOffsets: boolean workingOffsets: WorkingOffset[] existingOffsets: LabwareOffset[] handleJog: Jog @@ -58,6 +70,7 @@ interface PickUpTipProps extends PickUpTipStep { protocolHasModules: boolean currentStepIndex: number } + export const PickUpTip = (props: PickUpTipProps): JSX.Element | null => { const { t, i18n } = useTranslation(['labware_position_check', 'shared']) const { @@ -70,8 +83,11 @@ export const PickUpTip = (props: PickUpTipProps): JSX.Element | null => { registerPosition, handleJog, isRobotMoving, + isApplyingOffsets, + calculateAndApplyOffset, existingOffsets, workingOffsets, + onSkip, setFatalError, adapterId, robotType, @@ -227,6 +243,7 @@ export const PickUpTip = (props: PickUpTipProps): JSX.Element | null => { initialPosition != null && position != null ? getVectorDifference(position, initialPosition) : undefined + registerPosition({ type: 'finalPosition', labwareId, @@ -234,7 +251,8 @@ export const PickUpTip = (props: PickUpTipProps): JSX.Element | null => { position, }) registerPosition({ type: 'tipPickUpOffset', offset: offset ?? null }) - chainRunCommands( + + return chainRunCommands( [ { commandType: 'pickUpTip', @@ -247,16 +265,20 @@ export const PickUpTip = (props: PickUpTipProps): JSX.Element | null => { }, ], false - ) - .then(() => { - setShowTipConfirmation(true) - }) - .catch((e: Error) => { - setFatalError( - `PickUpTip failed to move from final position with message: ${e.message}` + ).then(() => { + setShowTipConfirmation(true) + if (position != null) { + return calculateAndApplyOffset( + initialPosition ?? null, + position, + labwareId, + location ) - }) + } + return Promise.resolve() + }) } + return Promise.resolve() }) .catch((e: Error) => { setFatalError( @@ -435,7 +457,8 @@ export const PickUpTip = (props: PickUpTipProps): JSX.Element | null => { } labwareDef={labwareDef} pipetteName={pipetteName} - handleConfirmPosition={handleConfirmPosition} + handleConfirmPositionAndApply={handleConfirmPosition} + isApplyingOffsets={isApplyingOffsets} handleGoBack={handleGoBack} handleJog={handleJog} initialPosition={initialPosition} @@ -451,6 +474,7 @@ export const PickUpTip = (props: PickUpTipProps): JSX.Element | null => { })} body={} labwareDef={labwareDef} + onSkip={onSkip} confirmPlacement={handleConfirmPlacement} robotType={robotType} /> diff --git a/app/src/organisms/LegacyLabwarePositionCheck/ResultsSummary.tsx b/app/src/organisms/LegacyLabwarePositionCheck/ResultsSummary.tsx index bba350f408a9..168863dc11ff 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/ResultsSummary.tsx +++ b/app/src/organisms/LegacyLabwarePositionCheck/ResultsSummary.tsx @@ -1,4 +1,4 @@ -import { useMemo, Fragment } from 'react' +import { Fragment } from 'react' import styled from 'styled-components' import { useSelector } from 'react-redux' import isEqual from 'lodash/isEqual' @@ -7,8 +7,6 @@ import { getLabwareDefURI, getLabwareDisplayName, getModuleType, - getVectorDifference, - getVectorSum, IDENTITY_VECTOR, OT2_ROBOT_TYPE, } from '@opentrons/shared-data' @@ -40,7 +38,6 @@ import { } from '/app/redux/config' import { SmallButton } from '/app/atoms/buttons' import { LegacyLabwareOffsetTabs } from '/app/organisms/LegacyLabwareOffsetTabs' -import { getCurrentOffsetForLabwareInLocation } from '/app/transformations/analysis' import { getDisplayLocation } from './utils/getDisplayLocation' import type { @@ -61,8 +58,8 @@ interface ResultsSummaryProps extends ResultsSummaryStep { protocolData: CompletedProtocolAnalysis workingOffsets: WorkingOffset[] existingOffsets: LabwareOffset[] - handleApplyOffsets: (offsets: LegacyLabwareOffsetCreateData[]) => void - isApplyingOffsets: boolean + allAppliedOffsets: LegacyLabwareOffsetCreateData[] + onCloseClick: () => void isDeletingMaintenanceRun?: boolean } export const ResultsSummary = ( @@ -71,71 +68,34 @@ export const ResultsSummary = ( const { i18n, t } = useTranslation('labware_position_check') const { protocolData, - workingOffsets, - handleApplyOffsets, - existingOffsets, - isApplyingOffsets, + allAppliedOffsets, + onCloseClick, isDeletingMaintenanceRun, } = props const labwareDefinitions = getLabwareDefinitionsFromCommands( protocolData.commands ) - const isSubmittingAndClosing = isApplyingOffsets || isDeletingMaintenanceRun + const isSubmittingAndClosing = isDeletingMaintenanceRun const isLabwareOffsetCodeSnippetsOn = useSelector( getIsLabwareOffsetCodeSnippetsOn ) const isOnDevice = useSelector(getIsOnDevice) - const offsetsToApply = useMemo(() => { - return workingOffsets.map( - ({ initialPosition, finalPosition, labwareId, location }) => { - const definitionUri = - protocolData.labware.find(l => l.id === labwareId)?.definitionUri ?? - null - if ( - finalPosition == null || - initialPosition == null || - definitionUri == null - ) { - throw new Error( - `cannot create offset for labware with id ${labwareId}, in location ${JSON.stringify( - location - )}, with initial position ${String( - initialPosition - )}, and final position ${String(finalPosition)}` - ) - } - - const existingOffset = - getCurrentOffsetForLabwareInLocation( - existingOffsets, - definitionUri, - location - )?.vector ?? IDENTITY_VECTOR - const vector = getVectorSum( - existingOffset, - getVectorDifference(finalPosition, initialPosition) - ) - return { definitionUri, location, vector } - } - ) - }, [workingOffsets]) - const TableComponent = isOnDevice ? ( ) : ( ) const JupyterSnippet = ( { - handleApplyOffsets(offsetsToApply) - }} buttonText={i18n.format(t('apply_offsets'), 'capitalize')} iconName={isSubmittingAndClosing ? 'ot-spinner' : null} iconPlacement={isSubmittingAndClosing ? 'startIcon' : null} - disabled={isSubmittingAndClosing} + onClick={onCloseClick} /> ) : ( - { - handleApplyOffsets(offsetsToApply) - }} - disabled={isSubmittingAndClosing} - > + {isSubmittingAndClosing ? ( void chainRunCommands: ReturnType['chainRunCommands'] setFatalError: (errorMessage: string) => void + onSkip: () => void tipPickUpOffset: VectorOffset | null isRobotMoving: boolean robotType: RobotType @@ -51,6 +52,7 @@ export const ReturnTip = (props: ReturnTipProps): JSX.Element | null => { tipPickUpOffset, isRobotMoving, chainRunCommands, + onSkip, setFatalError, adapterId, } = props @@ -222,6 +224,7 @@ export const ReturnTip = (props: ReturnTipProps): JSX.Element | null => { body={} labwareDef={labwareDef} confirmPlacement={handleConfirmPlacement} + onSkip={onSkip} /> ) diff --git a/app/src/organisms/LegacyLabwarePositionCheck/__tests__/CheckItem.test.tsx b/app/src/organisms/LegacyLabwarePositionCheck/__tests__/CheckItem.test.tsx index 5c2907c52764..6e8274b13984 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/__tests__/CheckItem.test.tsx +++ b/app/src/organisms/LegacyLabwarePositionCheck/__tests__/CheckItem.test.tsx @@ -32,9 +32,11 @@ const render = (props: ComponentProps) => { describe('CheckItem', () => { let props: ComponentProps let mockChainRunCommands: Mock + let mockApplyMostRecentWorkingOffset: Mock beforeEach(() => { mockChainRunCommands = vi.fn().mockImplementation(() => Promise.resolve([])) + mockApplyMostRecentWorkingOffset = vi.fn() props = { section: SECTIONS.CHECK_LABWARE, pipetteId: mockCompletedAnalysis.pipettes[0].id, @@ -52,6 +54,8 @@ describe('CheckItem', () => { isRobotMoving: false, robotType: FLEX_ROBOT_TYPE, shouldUseMetalProbe: false, + calculateAndApplyOffset: mockApplyMostRecentWorkingOffset, + isApplyingOffsets: false, } }) afterEach(() => { @@ -430,6 +434,7 @@ describe('CheckItem', () => { location: { slotName: 'D1' }, position: mockEndPosition, }) + expect(mockApplyMostRecentWorkingOffset).toHaveBeenCalled() }) it('executes heater shaker open latch command on component mount if step is on HS', async () => { @@ -613,6 +618,8 @@ describe('CheckItem', () => { location: { slotName: 'D1', moduleModel: HEATERSHAKER_MODULE_V1 }, position: mockEndPosition, }) + + expect(mockApplyMostRecentWorkingOffset).toHaveBeenCalled() }) it('executes thermocycler open lid command on mount if checking labware on thermocycler', () => { diff --git a/app/src/organisms/LegacyLabwarePositionCheck/__tests__/PickUpTip.test.tsx b/app/src/organisms/LegacyLabwarePositionCheck/__tests__/PickUpTip.test.tsx index 286e6f5f0950..b84d0a01716a 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/__tests__/PickUpTip.test.tsx +++ b/app/src/organisms/LegacyLabwarePositionCheck/__tests__/PickUpTip.test.tsx @@ -27,9 +27,11 @@ const render = (props: ComponentProps) => { describe('PickUpTip', () => { let props: ComponentProps let mockChainRunCommands: Mock + let mockCalculateAndApplyOffset: Mock beforeEach(() => { mockChainRunCommands = vi.fn().mockImplementation(() => Promise.resolve()) + mockCalculateAndApplyOffset = vi.fn() vi.mocked(getIsOnDevice).mockReturnValue(false) props = { section: SECTIONS.PICK_UP_TIP, @@ -49,6 +51,9 @@ describe('PickUpTip', () => { robotType: FLEX_ROBOT_TYPE, protocolHasModules: false, currentStepIndex: 1, + onSkip: vi.fn(), + isApplyingOffsets: false, + calculateAndApplyOffset: mockCalculateAndApplyOffset, } vi.mocked(useProtocolMetadata).mockReturnValue({ robotType: 'OT-3 Standard', @@ -250,10 +255,16 @@ describe('PickUpTip', () => { ], false ) + screen.getByRole('heading', { name: 'Did pipette pick up tip successfully?', }) }) + + await waitFor(() => { + expect(mockCalculateAndApplyOffset).toHaveBeenCalled() + }) + const tryAgain = screen.getByRole('button', { name: 'Try again' }) fireEvent.click(tryAgain) await new Promise((resolve, reject) => setTimeout(resolve)) @@ -365,6 +376,11 @@ describe('PickUpTip', () => { name: 'Did pipette pick up tip successfully?', }) }) + + await waitFor(() => { + expect(mockCalculateAndApplyOffset).toHaveBeenCalled() + }) + const yesButton = screen.getByRole('button', { name: 'Yes' }) fireEvent.click(yesButton) await new Promise((resolve, reject) => setTimeout(resolve)) diff --git a/app/src/organisms/LegacyLabwarePositionCheck/__tests__/ResultsSummary.test.tsx b/app/src/organisms/LegacyLabwarePositionCheck/__tests__/ResultsSummary.test.tsx index 30a29496aaf1..aaf98cc8d022 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/__tests__/ResultsSummary.test.tsx +++ b/app/src/organisms/LegacyLabwarePositionCheck/__tests__/ResultsSummary.test.tsx @@ -13,6 +13,7 @@ import { } from '../__fixtures__' import type { ComponentProps } from 'react' +import type { Mock } from 'vitest' vi.mock('/app/redux/config') @@ -24,16 +25,19 @@ const render = (props: ComponentProps) => { describe('ResultsSummary', () => { let props: ComponentProps + let mockOnCloseClick: Mock beforeEach(() => { + mockOnCloseClick = vi.fn() + props = { section: SECTIONS.RESULTS_SUMMARY, protocolData: mockCompletedAnalysis, workingOffsets: mockWorkingOffsets, existingOffsets: mockExistingOffsets, - isApplyingOffsets: false, isDeletingMaintenanceRun: false, - handleApplyOffsets: vi.fn(), + allAppliedOffsets: [], + onCloseClick: mockOnCloseClick, } }) afterEach(() => { @@ -42,32 +46,24 @@ describe('ResultsSummary', () => { it('renders correct copy', () => { render(props) screen.getByText('New labware offset data') - screen.getByRole('button', { name: 'Apply offsets' }) + screen.getByRole('button', { name: 'Finish' }) screen.getByRole('link', { name: 'Need help?' }) screen.getByRole('columnheader', { name: 'location' }) screen.getByRole('columnheader', { name: 'labware' }) screen.getByRole('columnheader', { name: 'labware offset data' }) }) - it('calls handle apply offsets function when button is clicked', () => { - render(props) - fireEvent.click(screen.getByRole('button', { name: 'Apply offsets' })) - expect(props.handleApplyOffsets).toHaveBeenCalled() - }) - it('does disables the CTA to apply offsets when offsets are already being applied', () => { - props.isApplyingOffsets = true + it('calls on close function when button is clicked', () => { render(props) - const button = screen.getByRole('button', { name: 'Apply offsets' }) - expect(button).toBeDisabled() - fireEvent.click(button) - expect(props.handleApplyOffsets).not.toHaveBeenCalled() + fireEvent.click(screen.getByRole('button', { name: 'Finish' })) + expect(mockOnCloseClick).toHaveBeenCalled() }) it('does disables the CTA to apply offsets when the maintenance run is being deleted', () => { props.isDeletingMaintenanceRun = true render(props) - const button = screen.getByRole('button', { name: 'Apply offsets' }) + const button = screen.getByRole('button', { name: 'Finish' }) expect(button).toBeDisabled() fireEvent.click(button) - expect(props.handleApplyOffsets).not.toHaveBeenCalled() + expect(mockOnCloseClick).not.toHaveBeenCalled() }) it('renders a row per offset to apply', () => { render(props) diff --git a/app/src/organisms/LegacyLabwarePositionCheck/__tests__/ReturnTip.test.tsx b/app/src/organisms/LegacyLabwarePositionCheck/__tests__/ReturnTip.test.tsx index 112be630a314..3893e7f60bde 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/__tests__/ReturnTip.test.tsx +++ b/app/src/organisms/LegacyLabwarePositionCheck/__tests__/ReturnTip.test.tsx @@ -42,6 +42,7 @@ describe('ReturnTip', () => { tipPickUpOffset: null, isRobotMoving: false, robotType: FLEX_ROBOT_TYPE, + onSkip: vi.fn(), } vi.mocked(useProtocolMetadata).mockReturnValue({ robotType: 'OT-3 Standard', From 012cee85f610c7be5e2a0dec3cf10c198004042c Mon Sep 17 00:00:00 2001 From: Jamey Huffnagle Date: Wed, 26 Mar 2025 17:42:06 -0400 Subject: [PATCH 03/10] refactor(app): update OT-2 LPC exit confirmation copy --- app/src/assets/localization/en/labware_position_check.json | 2 +- .../__tests__/ExitConfirmation.test.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/assets/localization/en/labware_position_check.json b/app/src/assets/localization/en/labware_position_check.json index 5ee876a5e003..64a597374a31 100644 --- a/app/src/assets/localization/en/labware_position_check.json +++ b/app/src/assets/localization/en/labware_position_check.json @@ -54,7 +54,7 @@ "exit": "Exit", "exit_screen_confirm_exit": "Exit and discard all labware offsets", "exit_screen_go_back": "Go back to labware position check", - "exit_screen_subtitle": "If you exit now, all labware offsets will be discarded. This cannot be undone.", + "exit_screen_subtitle": "If you exit now, only the labware offsets you've confirmed will be applied to this run.", "exit_screen_title": "Exit before completing Labware Position Check?", "failed_to_save_final_position": "Failed to save final position", "get_labware_offset_data": "Get Labware Offset Data", diff --git a/app/src/organisms/LegacyLabwarePositionCheck/__tests__/ExitConfirmation.test.tsx b/app/src/organisms/LegacyLabwarePositionCheck/__tests__/ExitConfirmation.test.tsx index e710c991ccbd..b1b2371eca42 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/__tests__/ExitConfirmation.test.tsx +++ b/app/src/organisms/LegacyLabwarePositionCheck/__tests__/ExitConfirmation.test.tsx @@ -29,7 +29,7 @@ describe('ExitConfirmation', () => { render(props) screen.getByText('Exit before completing Labware Position Check?') screen.getByText( - 'If you exit now, all labware offsets will be discarded. This cannot be undone.' + "If you exit now, only the labware offsets you've confirmed will be applied to this run." ) screen.getByRole('button', { name: 'Exit' }) screen.getByRole('button', { name: 'Go back' }) @@ -48,7 +48,7 @@ describe('ExitConfirmation', () => { }) screen.getByText('Remove the calibration probe before exiting') screen.getByText( - 'If you exit now, all labware offsets will be discarded. This cannot be undone.' + "If you exit now, only the labware offsets you've confirmed will be applied to this run." ) screen.getByRole('button', { name: 'Remove calibration probe' }) screen.getByRole('button', { name: 'Go back' }) From 15f506ad1d474f626f3a8917c69f217ede944b5a Mon Sep 17 00:00:00 2001 From: Jamey Huffnagle Date: Thu, 27 Mar 2025 09:02:04 -0400 Subject: [PATCH 04/10] refactor(app): update the results summary screen --- .../en/labware_position_check.json | 1 + .../ResultsSummary.tsx | 9 ++++--- .../__tests__/ResultsSummary.test.tsx | 25 ++++++++++++------- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/app/src/assets/localization/en/labware_position_check.json b/app/src/assets/localization/en/labware_position_check.json index 64a597374a31..3fe6e816c96d 100644 --- a/app/src/assets/localization/en/labware_position_check.json +++ b/app/src/assets/localization/en/labware_position_check.json @@ -31,6 +31,7 @@ "clear_deck_all_lw_leave_modules": "Clear all deck slots of labware, leaving modules in place", "cli_ssh": "Command Line Interface (SSH)", "close_and_apply_offset_data": "Close and apply labware offset data", + "complete": "Complete", "confirm": "Confirm", "confirm_detached": "Confirm removal", "confirm_go_back_without_saving": "Are you sure you want to go back to the the labware list without saving?", diff --git a/app/src/organisms/LegacyLabwarePositionCheck/ResultsSummary.tsx b/app/src/organisms/LegacyLabwarePositionCheck/ResultsSummary.tsx index 168863dc11ff..48ec54212a98 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/ResultsSummary.tsx +++ b/app/src/organisms/LegacyLabwarePositionCheck/ResultsSummary.tsx @@ -136,7 +136,7 @@ export const ResultsSummary = ( {isOnDevice ? ( - + {isSubmittingAndClosing ? ( ) : null} - {i18n.format(t('apply_offsets'), 'capitalize')} + {i18n.format(t('complete'), 'capitalize')} diff --git a/app/src/organisms/LegacyLabwarePositionCheck/__tests__/ResultsSummary.test.tsx b/app/src/organisms/LegacyLabwarePositionCheck/__tests__/ResultsSummary.test.tsx index aaf98cc8d022..27890ef4cadc 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/__tests__/ResultsSummary.test.tsx +++ b/app/src/organisms/LegacyLabwarePositionCheck/__tests__/ResultsSummary.test.tsx @@ -36,7 +36,18 @@ describe('ResultsSummary', () => { workingOffsets: mockWorkingOffsets, existingOffsets: mockExistingOffsets, isDeletingMaintenanceRun: false, - allAppliedOffsets: [], + allAppliedOffsets: [ + { + location: { slotName: '1' }, + vector: { x: 1.0, y: 1.0, z: 1.0 }, + definitionUri: 'mock-uri', + }, + { + location: { slotName: '3' }, + vector: { x: 3.0, y: 3.0, z: 3.0 }, + definitionUri: 'mock-uri-2', + }, + ], onCloseClick: mockOnCloseClick, } }) @@ -46,7 +57,7 @@ describe('ResultsSummary', () => { it('renders correct copy', () => { render(props) screen.getByText('New labware offset data') - screen.getByRole('button', { name: 'Finish' }) + screen.getByRole('button', { name: 'Complete' }) screen.getByRole('link', { name: 'Need help?' }) screen.getByRole('columnheader', { name: 'location' }) screen.getByRole('columnheader', { name: 'labware' }) @@ -54,24 +65,20 @@ describe('ResultsSummary', () => { }) it('calls on close function when button is clicked', () => { render(props) - fireEvent.click(screen.getByRole('button', { name: 'Finish' })) + fireEvent.click(screen.getByRole('button', { name: 'Complete' })) expect(mockOnCloseClick).toHaveBeenCalled() }) it('does disables the CTA to apply offsets when the maintenance run is being deleted', () => { props.isDeletingMaintenanceRun = true render(props) - const button = screen.getByRole('button', { name: 'Finish' }) + const button = screen.getByRole('button', { name: 'Complete' }) expect(button).toBeDisabled() fireEvent.click(button) expect(mockOnCloseClick).not.toHaveBeenCalled() }) it('renders a row per offset to apply', () => { render(props) - expect( - screen.queryAllByRole('cell', { - name: mockTipRackDefinition.metadata.displayName, - }) - ).toHaveLength(2) + screen.getByRole('cell', { name: 'Slot 1' }) screen.getByRole('cell', { name: 'Slot 3' }) screen.getByRole('cell', { name: 'X 1.0 Y 1.0 Z 1.0' }) From 4677c375cd75e2d20360b0807e36280cbae23cff Mon Sep 17 00:00:00 2001 From: Jamey Huffnagle Date: Thu, 27 Mar 2025 09:15:08 -0400 Subject: [PATCH 05/10] refactor(app): ensure closing state is passed to the OT-2 flows --- .../organisms/LabwarePositionCheck/LPCFlows/LPCFlows.tsx | 1 + .../organisms/LabwarePositionCheck/LPCFlows/useLPCFlows.ts | 6 +++++- .../organisms/LabwarePositionCheck/LPCWizardContainer.tsx | 1 + .../LegacyLabwarePositionCheckComponent.tsx | 2 +- app/src/organisms/LegacyLabwarePositionCheck/index.tsx | 2 +- 5 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/organisms/LabwarePositionCheck/LPCFlows/LPCFlows.tsx b/app/src/organisms/LabwarePositionCheck/LPCFlows/LPCFlows.tsx index f5653fb3d6b1..8f23132fe319 100644 --- a/app/src/organisms/LabwarePositionCheck/LPCFlows/LPCFlows.tsx +++ b/app/src/organisms/LabwarePositionCheck/LPCFlows/LPCFlows.tsx @@ -16,6 +16,7 @@ export interface LegacySupportLPCFlowsProps extends LPCFlowsProps { export interface LPCFlowsProps { onCloseClick: () => void + isClosing: boolean runId: string robotType: RobotType deckConfig: DeckConfiguration diff --git a/app/src/organisms/LabwarePositionCheck/LPCFlows/useLPCFlows.ts b/app/src/organisms/LabwarePositionCheck/LPCFlows/useLPCFlows.ts index 9db0061bcf32..cd17862caba5 100644 --- a/app/src/organisms/LabwarePositionCheck/LPCFlows/useLPCFlows.ts +++ b/app/src/organisms/LabwarePositionCheck/LPCFlows/useLPCFlows.ts @@ -120,7 +120,10 @@ export function useLPCFlows({ const { createLabwareDefinition, } = useCreateMaintenanceRunLabwareDefinitionMutation() - const { deleteMaintenanceRun } = useDeleteMaintenanceRunMutation() + const { + deleteMaintenanceRun, + isLoading: isClosing, + } = useDeleteMaintenanceRunMutation() useRunLoadedLabwareDefinitions(runId ?? null, { onSuccess: res => { void Promise.all( @@ -193,6 +196,7 @@ export function useLPCFlows({ showLPC, lpcProps: { onCloseClick: handleCloseLPC, + isClosing, runId, robotType, deckConfig, diff --git a/app/src/organisms/LabwarePositionCheck/LPCWizardContainer.tsx b/app/src/organisms/LabwarePositionCheck/LPCWizardContainer.tsx index 5bf68560df5d..92191fb90c97 100644 --- a/app/src/organisms/LabwarePositionCheck/LPCWizardContainer.tsx +++ b/app/src/organisms/LabwarePositionCheck/LPCWizardContainer.tsx @@ -17,6 +17,7 @@ export function LPCWizardContainer( {...props} existingOffsets={props.ot2Offsets} mostRecentAnalysis={props.analysis} + isDeletingMaintenanceRun={props.isClosing} /> ) default: { diff --git a/app/src/organisms/LegacyLabwarePositionCheck/LegacyLabwarePositionCheckComponent.tsx b/app/src/organisms/LegacyLabwarePositionCheck/LegacyLabwarePositionCheckComponent.tsx index 0ec60aab6527..57778b348da0 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/LegacyLabwarePositionCheckComponent.tsx +++ b/app/src/organisms/LegacyLabwarePositionCheck/LegacyLabwarePositionCheckComponent.tsx @@ -64,8 +64,8 @@ interface LegacyLabwarePositionCheckModalProps { existingOffsets: LabwareOffset[] onCloseClick: () => unknown protocolName: string + isDeletingMaintenanceRun: boolean setMaintenanceRunId?: (id: string | null) => void - isDeletingMaintenanceRun?: boolean caughtError?: Error } diff --git a/app/src/organisms/LegacyLabwarePositionCheck/index.tsx b/app/src/organisms/LegacyLabwarePositionCheck/index.tsx index 48bb3410e5d8..0ff9749c5a57 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/index.tsx +++ b/app/src/organisms/LegacyLabwarePositionCheck/index.tsx @@ -23,7 +23,7 @@ interface LabwarePositionCheckModalProps { mostRecentAnalysis: CompletedProtocolAnalysis | null protocolName: string setMaintenanceRunId?: (id: string | null) => void - isDeletingMaintenanceRun?: boolean + isDeletingMaintenanceRun: boolean caughtError?: Error } From b69c9d173914a89a25b39b87fd5d6f1ce98ce7cc Mon Sep 17 00:00:00 2001 From: Jamey Huffnagle Date: Thu, 27 Mar 2025 12:35:21 -0400 Subject: [PATCH 06/10] refactor(app): actually, we can skip on any non pick up tip step --- app/src/organisms/LegacyLabwarePositionCheck/PrepareSpace.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/organisms/LegacyLabwarePositionCheck/PrepareSpace.tsx b/app/src/organisms/LegacyLabwarePositionCheck/PrepareSpace.tsx index 55dc3d2dbf63..3bf75a5b5ea1 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/PrepareSpace.tsx +++ b/app/src/organisms/LegacyLabwarePositionCheck/PrepareSpace.tsx @@ -143,7 +143,7 @@ export const PrepareSpace = (props: PrepareSpaceProps): JSX.Element | null => { - {section === 'CHECK_LABWARE' && ( + {section !== 'PICK_UP_TIP' && section !== 'RETURN_TIP' && ( {t('skip')} )} From e9a74be38f22f48ab85305f3b92dd1c25b724885 Mon Sep 17 00:00:00 2001 From: Jamey Huffnagle Date: Thu, 27 Mar 2025 13:22:39 -0400 Subject: [PATCH 07/10] lint --- app/src/organisms/LegacyLabwarePositionCheck/CheckItem.tsx | 2 -- .../__tests__/ResultsSummary.test.tsx | 1 - 2 files changed, 3 deletions(-) diff --git a/app/src/organisms/LegacyLabwarePositionCheck/CheckItem.tsx b/app/src/organisms/LegacyLabwarePositionCheck/CheckItem.tsx index 7faa04696364..c65029cf1553 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/CheckItem.tsx +++ b/app/src/organisms/LegacyLabwarePositionCheck/CheckItem.tsx @@ -21,8 +21,6 @@ import { HEATERSHAKER_MODULE_TYPE, IDENTITY_VECTOR, THERMOCYCLER_MODULE_TYPE, - getVectorDifference, - getVectorSum, } from '@opentrons/shared-data' import { useSelector } from 'react-redux' import { getLabwareDef } from './utils/labware' diff --git a/app/src/organisms/LegacyLabwarePositionCheck/__tests__/ResultsSummary.test.tsx b/app/src/organisms/LegacyLabwarePositionCheck/__tests__/ResultsSummary.test.tsx index 27890ef4cadc..97ac2a93c5ea 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/__tests__/ResultsSummary.test.tsx +++ b/app/src/organisms/LegacyLabwarePositionCheck/__tests__/ResultsSummary.test.tsx @@ -5,7 +5,6 @@ import { renderWithProviders } from '/app/__testing-utils__' import { getIsLabwareOffsetCodeSnippetsOn } from '/app/redux/config' import { ResultsSummary } from '../ResultsSummary' import { SECTIONS } from '../constants' -import { mockTipRackDefinition } from '/app/redux/custom-labware/__fixtures__' import { mockCompletedAnalysis, mockExistingOffsets, From bbb46357eabaaac4f224a98d9932c4c011fdb64b Mon Sep 17 00:00:00 2001 From: Jamey Huffnagle Date: Thu, 27 Mar 2025 16:08:05 -0400 Subject: [PATCH 08/10] add a spinner when lpc is loading --- .../OT2SetupLPC/index.tsx | 17 ++++++++++++++--- .../ResultsSummary.tsx | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/OT2SetupLPC/index.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/OT2SetupLPC/index.tsx index 511ef844d694..c2428c7cf372 100644 --- a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/OT2SetupLPC/index.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/OT2SetupLPC/index.tsx @@ -15,6 +15,7 @@ import { Tooltip, TYPOGRAPHY, useHoverTooltip, + Icon, } from '@opentrons/components' import { useProtocolQuery } from '@opentrons/react-api-client' @@ -80,7 +81,7 @@ export function OT2SetupLPC( placement: TOOLTIP_LEFT, }) - const { launchLPC, lpcProps, showLPC } = useLPCFlows({ + const { launchLPC, lpcProps, showLPC, isLaunchingLPC } = useLPCFlows({ runId, robotType, protocolName, @@ -142,9 +143,19 @@ export function OT2SetupLPC( onClick={launchLPC} id="LabwareSetup_checkLabwarePositionsButton" {...runLPCTargetProps} - disabled={lpcDisabledReason !== null} + disabled={lpcDisabledReason !== null || isLaunchingLPC} > - {t('run_labware_position_check')} + + {isLaunchingLPC && ( + + )} + {t('run_labware_position_check')} + {lpcDisabledReason !== null ? ( diff --git a/app/src/organisms/LegacyLabwarePositionCheck/ResultsSummary.tsx b/app/src/organisms/LegacyLabwarePositionCheck/ResultsSummary.tsx index 48ec54212a98..a6716eb7b18e 100644 --- a/app/src/organisms/LegacyLabwarePositionCheck/ResultsSummary.tsx +++ b/app/src/organisms/LegacyLabwarePositionCheck/ResultsSummary.tsx @@ -153,7 +153,7 @@ export const ResultsSummary = ( onClick={onCloseClick} disabled={isSubmittingAndClosing} > - + {isSubmittingAndClosing ? ( Date: Thu, 27 Mar 2025 16:24:13 -0400 Subject: [PATCH 09/10] Design table change --- app/src/assets/localization/en/labware_position_check.json | 2 +- app/src/organisms/LabwareOffsetsTable/AccordionDetail.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/assets/localization/en/labware_position_check.json b/app/src/assets/localization/en/labware_position_check.json index 3fe6e816c96d..85da36f38f84 100644 --- a/app/src/assets/localization/en/labware_position_check.json +++ b/app/src/assets/localization/en/labware_position_check.json @@ -43,7 +43,6 @@ "confirm_position_and_return_tip": "Confirm position, return tip to Slot {{next_slot}} and home", "confirm_removal": "Confirm removal", "continue": "Continue", - "custom": "Custom", "default": "Default", "default_labware_offset": "Default Labware Offset", "default_location_offset_added": "Default location offset added", @@ -111,6 +110,7 @@ "location_header": "Location", "lpc_complete": "Labware Position Check complete", "lpc_complete_summary_screen_heading": "Labware Position Check Complete", + "manual": "Manual", "modify_hardcoded_offsets_in_protocol": "Hardcoded offsets must be changed in your Python protocol", "module_display_location_text": "{{moduleName}} in Deck Slot {{slot}}", "module_in_slot": "{{module}} in {{slot}}", diff --git a/app/src/organisms/LabwareOffsetsTable/AccordionDetail.tsx b/app/src/organisms/LabwareOffsetsTable/AccordionDetail.tsx index d6c4c53f026b..6f41f20f092c 100644 --- a/app/src/organisms/LabwareOffsetsTable/AccordionDetail.tsx +++ b/app/src/organisms/LabwareOffsetsTable/AccordionDetail.tsx @@ -35,7 +35,7 @@ export function AccordionDetail({ const buildColTwoText = (): string => { if (isHardcoded || lsExistingOffset?.vector != null) { - return t('custom') + return t('manual') } else if (defaultExistingOffset?.vector != null) { return t('default') } else { From 12decdf53550528f401c111e71de88b1341b89cf Mon Sep 17 00:00:00 2001 From: Jamey Huffnagle Date: Thu, 27 Mar 2025 13:35:40 -0400 Subject: [PATCH 10/10] fix(app): show lpc disabled reason as a tooltip if it exists --- .../SetupLabwarePositionCheck/FlexSetupLPC/LPCSetupFlexBtns.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/FlexSetupLPC/LPCSetupFlexBtns.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/FlexSetupLPC/LPCSetupFlexBtns.tsx index eeb4c035423f..c9fc8fa24ac1 100644 --- a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/FlexSetupLPC/LPCSetupFlexBtns.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/FlexSetupLPC/LPCSetupFlexBtns.tsx @@ -70,6 +70,8 @@ export function LPCSetupFlexBtns({ return t('add_missing_labware_offsets') } else if (offsetsConfirmed) { return t('offsets_already_applied') + } else if (lpcDisabledReason != null) { + return lpcDisabledReason } else if (!anyOffsetsToLpc) { return t('no_offsets_found') } else {