diff --git a/app/src/molecules/Command/Command.tsx b/app/src/molecules/Command/Command.tsx index 32d95a33371..3b09498ca00 100644 --- a/app/src/molecules/Command/Command.tsx +++ b/app/src/molecules/Command/Command.tsx @@ -8,7 +8,11 @@ import { SPACING, RESPONSIVENESS, } from '@opentrons/components' -import type { RobotType, RunTimeCommand } from '@opentrons/shared-data' +import type { + LabwareDefinition2, + RobotType, + RunTimeCommand, +} from '@opentrons/shared-data' import { CommandText } from './CommandText' import { CommandIcon } from './CommandIcon' import type { CommandTextData } from './types' @@ -34,6 +38,7 @@ interface SkeletonCommandProps extends FundamentalProps { interface NonSkeletonCommandProps extends FundamentalProps { state: NonSkeletonCommandState command: RunTimeCommand + allRunDefs: LabwareDefinition2[] commandTextData: CommandTextData } diff --git a/app/src/molecules/Command/CommandText.tsx b/app/src/molecules/Command/CommandText.tsx index 00c7337104b..3e8b27d2522 100644 --- a/app/src/molecules/Command/CommandText.tsx +++ b/app/src/molecules/Command/CommandText.tsx @@ -13,7 +13,11 @@ import { import { useCommandTextString } from './hooks' -import type { RobotType, RunTimeCommand } from '@opentrons/shared-data' +import type { + LabwareDefinition2, + RobotType, + RunTimeCommand, +} from '@opentrons/shared-data' import type { StyleProps } from '@opentrons/components' import type { CommandTextData } from './types' import type { @@ -36,6 +40,7 @@ type STProps = LegacySTProps | ModernSTProps interface BaseProps extends StyleProps { command: RunTimeCommand + allRunDefs: LabwareDefinition2[] commandTextData: CommandTextData robotType: RobotType isOnDevice?: boolean diff --git a/app/src/molecules/Command/__tests__/CommandText.test.tsx b/app/src/molecules/Command/__tests__/CommandText.test.tsx index a6614c6b330..621208af0a9 100644 --- a/app/src/molecules/Command/__tests__/CommandText.test.tsx +++ b/app/src/molecules/Command/__tests__/CommandText.test.tsx @@ -41,6 +41,7 @@ describe('CommandText', () => { if (command != null) { renderWithProviders( { if (command != null) { renderWithProviders( { if (pushOutDispenseCommand != null) { renderWithProviders( { it('renders correct text for dispenseInPlace', () => { renderWithProviders( { if (blowoutCommand != null) { renderWithProviders( { it('renders correct text for blowOutInPlace', () => { renderWithProviders( { it('renders correct text for aspirateInPlace', () => { renderWithProviders( { if (moveToWellCommand != null) { renderWithProviders( { it('renders correct text for labware involving an addressable area slot', () => { renderWithProviders( { it('renders correct text for moveToAddressableArea for Waste Chutes', () => { renderWithProviders( { it('renders correct text for moveToAddressableArea for Fixed Trash', () => { renderWithProviders( { it('renders correct text for moveToAddressableArea for Trash Bins', () => { renderWithProviders( { it('renders correct text for moveToAddressableAreaForDropTip for Trash Bin', () => { renderWithProviders( { it('renders correct text for moveToAddressableArea for slots', () => { renderWithProviders( { renderWithProviders( { renderWithProviders( { if (command != null) { renderWithProviders( { it('renders correct text for dropTip into a labware', () => { renderWithProviders( { it('renders correct text for dropTipInPlace', () => { renderWithProviders( { if (command != null) { renderWithProviders( { if (command != null) { renderWithProviders( { if (command != null) { renderWithProviders( { const loadLabwareCommand = loadLabwareCommands[0] renderWithProviders( { const loadTipRackCommand = loadLabwareCommands[2] renderWithProviders( { const loadOnModuleCommand = loadLabwareCommands[3] renderWithProviders( { it('renders correct text for loadLabware in adapter', () => { renderWithProviders( { } as LoadLabwareRunTimeCommand renderWithProviders( { if (reloadLabwareCommand != null) { renderWithProviders( { } renderWithProviders( { const mockTemp = 20 renderWithProviders( { const mockTemp = 20 renderWithProviders( { it('renders correct text for temperatureModule/waitForTemperature with no specified temp', () => { renderWithProviders( { const mockTemp = 20 renderWithProviders( { const mockTemp = 20 renderWithProviders( { const mockTemp = 20 renderWithProviders( { ] renderWithProviders( { ] renderWithProviders( { ] renderWithProviders( { ] renderWithProviders( { it('renders correct text for heaterShaker/setAndWaitForShakeSpeed', () => { renderWithProviders( { it('renders correct text for moveToSlot', () => { renderWithProviders( { it('renders correct text for moveRelative', () => { renderWithProviders( { it('renders correct text for moveToCoordinates', () => { renderWithProviders( { ([commandType, expectedCopy]) => { renderWithProviders( { it('renders correct text for waitForDuration', () => { renderWithProviders( { it('renders correct text for legacy pause with message', () => { renderWithProviders( { it('renders correct text for legacy pause without message', () => { renderWithProviders( { it('renders correct text for waitForResume with message', () => { renderWithProviders( { it('renders correct text for waitForResume without message', () => { renderWithProviders( { it('renders correct text for legacy delay with time', () => { renderWithProviders( { it('renders correct text for legacy delay wait for resume with message', () => { renderWithProviders( { it('renders correct text for legacy delay wait for resume without message', () => { renderWithProviders( { it('renders correct text for comment', () => { renderWithProviders( { it('renders correct text for custom command type with legacy command text', () => { renderWithProviders( { it('renders correct text for custom command type with arbitrary params', () => { renderWithProviders( { it('renders correct text for move labware manually off deck', () => { renderWithProviders( { it('renders correct text for move labware manually to module', () => { renderWithProviders( { it('renders correct text for move labware with gripper off deck', () => { renderWithProviders( { it('renders correct text for move labware with gripper to waste chute', () => { renderWithProviders( { it('renders correct text for move labware with gripper to module', () => { renderWithProviders( { if (command != null) { renderWithProviders( { if (command != null) { renderWithProviders( = Omit< GetCommandText, 'command' -> & { command: T } +> & + UseCommandTextStringParams & { command: T } diff --git a/app/src/molecules/Command/utils/accessors.ts b/app/src/molecules/Command/utils/accessors.ts index 2ca6fda7efb..651fb15769e 100644 --- a/app/src/molecules/Command/utils/accessors.ts +++ b/app/src/molecules/Command/utils/accessors.ts @@ -26,7 +26,10 @@ export function getLoadedPipette( : commandTextData.pipettes[mount] } export function getLoadedModule( - commandTextData: CompletedProtocolAnalysis | RunData | CommandTextData, + commandTextData: + | CompletedProtocolAnalysis + | RunData + | Omit, moduleId: string ): LoadedModule | undefined { // NOTE: old analysis contains a object dictionary of module entities by id, this case is supported for backwards compatibility purposes diff --git a/app/src/molecules/Command/utils/getLabwareDisplayLocation.ts b/app/src/molecules/Command/utils/getLabwareDisplayLocation.ts index f86ff3473a8..724775fcc9e 100644 --- a/app/src/molecules/Command/utils/getLabwareDisplayLocation.ts +++ b/app/src/molecules/Command/utils/getLabwareDisplayLocation.ts @@ -5,15 +5,21 @@ import { getModuleType, getOccludedSlotCountForModule, } from '@opentrons/shared-data' + import { getModuleDisplayLocation } from './getModuleDisplayLocation' import { getModuleModel } from './getModuleModel' -import { getLabwareDefinitionsFromCommands } from './getLabwareDefinitionsFromCommands' -import type { RobotType, LabwareLocation } from '@opentrons/shared-data' + import type { TFunction } from 'i18next' +import type { + RobotType, + LabwareLocation, + LabwareDefinition2, +} from '@opentrons/shared-data' import type { CommandTextData } from '../types' export function getLabwareDisplayLocation( - commandTextData: CommandTextData, + commandTextData: Omit, + allRunDefs: LabwareDefinition2[], location: LabwareLocation, t: TFunction, robotType: RobotType, @@ -54,8 +60,7 @@ export function getLabwareDisplayLocation( const adapter = commandTextData.labware.find( lw => lw.id === location.labwareId ) - const allDefs = getLabwareDefinitionsFromCommands(commandTextData.commands) - const adapterDef = allDefs.find( + const adapterDef = allRunDefs.find( def => getLabwareDefURI(def) === adapter?.definitionUri ) const adapterDisplayName = diff --git a/app/src/molecules/Command/utils/getModuleDisplayLocation.ts b/app/src/molecules/Command/utils/getModuleDisplayLocation.ts index c71c74c4c86..fa5e527d218 100644 --- a/app/src/molecules/Command/utils/getModuleDisplayLocation.ts +++ b/app/src/molecules/Command/utils/getModuleDisplayLocation.ts @@ -3,7 +3,7 @@ import { getLoadedModule } from './accessors' import type { CommandTextData } from '../types' export function getModuleDisplayLocation( - commandTextData: CommandTextData, + commandTextData: Omit, moduleId: string ): string { const loadedModule = getLoadedModule(commandTextData, moduleId) diff --git a/app/src/molecules/Command/utils/getModuleModel.ts b/app/src/molecules/Command/utils/getModuleModel.ts index 3e95e05ebeb..fdac4850331 100644 --- a/app/src/molecules/Command/utils/getModuleModel.ts +++ b/app/src/molecules/Command/utils/getModuleModel.ts @@ -4,7 +4,7 @@ import type { ModuleModel } from '@opentrons/shared-data' import type { CommandTextData } from '../types' export function getModuleModel( - commandTextData: CommandTextData, + commandTextData: Omit, moduleId: string ): ModuleModel | null { const loadedModule = getLoadedModule(commandTextData, moduleId) diff --git a/app/src/molecules/InterventionModal/CategorizedStepContent.tsx b/app/src/molecules/InterventionModal/CategorizedStepContent.tsx index f0b78a256af..f1c0835d396 100644 --- a/app/src/molecules/InterventionModal/CategorizedStepContent.tsx +++ b/app/src/molecules/InterventionModal/CategorizedStepContent.tsx @@ -1,7 +1,5 @@ import { css } from 'styled-components' -import { Command, CommandIndex } from '../Command' -import type { NonSkeletonCommandState, CommandTextData } from '../Command' -import type { RobotType, RunTimeCommand } from '@opentrons/shared-data' + import { StyledText, Flex, @@ -11,6 +9,15 @@ import { RESPONSIVENESS, } from '@opentrons/components' +import { Command, CommandIndex } from '../Command' + +import type { NonSkeletonCommandState, CommandTextData } from '../Command' +import type { + LabwareDefinition2, + RobotType, + RunTimeCommand, +} from '@opentrons/shared-data' + export interface CommandWithIndex { index: number | undefined command: RunTimeCommand @@ -19,6 +26,7 @@ export interface CommandWithIndex { export interface CategorizedStepContentProps { robotType: RobotType commandTextData: CommandTextData | null + allRunDefs: LabwareDefinition2[] topCategoryHeadline: string topCategory: NonSkeletonCommandState topCategoryCommand: CommandWithIndex | null @@ -35,6 +43,7 @@ const EMPTY_COMMAND = { command: null, state: 'loading', commandTextData: null, + allRunDefs: [], } as const type MappedState = @@ -42,17 +51,19 @@ type MappedState = command: RunTimeCommand state: NonSkeletonCommandState commandTextData: CommandTextData + allRunDefs: LabwareDefinition2[] } | typeof EMPTY_COMMAND const commandAndState = ( command: CommandWithIndex | null, state: NonSkeletonCommandState, - commandTextData: CommandTextData | null + commandTextData: CommandTextData | null, + allRunDefs: LabwareDefinition2[] ): MappedState => command == null || commandTextData == null ? EMPTY_COMMAND - : { state, command: command.command, commandTextData } + : { state, command: command.command, commandTextData, allRunDefs } export function CategorizedStepContent( props: CategorizedStepContentProps @@ -97,7 +108,8 @@ export function CategorizedStepContent( {...commandAndState( props.topCategoryCommand, props.topCategory, - props.commandTextData + props.commandTextData, + props.allRunDefs )} robotType={props.robotType} aligned="left" @@ -137,7 +149,8 @@ export function CategorizedStepContent( {...commandAndState( command, props.bottomCategory, - props.commandTextData + props.commandTextData, + props.allRunDefs )} robotType={props.robotType} aligned="left" diff --git a/app/src/molecules/InterventionModal/InterventionContent/InterventionInfo.tsx b/app/src/molecules/InterventionModal/InterventionContent/InterventionInfo.tsx index 03435fb48b5..0d9c0d2a191 100644 --- a/app/src/molecules/InterventionModal/InterventionContent/InterventionInfo.tsx +++ b/app/src/molecules/InterventionModal/InterventionContent/InterventionInfo.tsx @@ -82,7 +82,7 @@ const buildContent = (props: InterventionInfoProps): JSX.Element => { } const buildLocArrowLoc = (props: InterventionInfoProps): JSX.Element => { - const { currentLocationProps, newLocationProps } = props + const { currentLocationProps, newLocationProps, type } = props if (newLocationProps != null) { return ( @@ -101,6 +101,9 @@ const buildLocArrowLoc = (props: InterventionInfoProps): JSX.Element => { ) } else { + console.error( + `InterventionInfo type is ${type}, but no newLocation was specified.` + ) return buildLoc(props) } } @@ -116,7 +119,7 @@ const buildLoc = ({ } const buildLocColonLoc = (props: InterventionInfoProps): JSX.Element => { - const { currentLocationProps, newLocationProps } = props + const { currentLocationProps, newLocationProps, type } = props if (newLocationProps != null) { return ( @@ -135,6 +138,9 @@ const buildLocColonLoc = (props: InterventionInfoProps): JSX.Element => { ) } else { + console.error( + `InterventionInfo type is ${type}, but no newLocation was specified.` + ) return buildLoc(props) } } diff --git a/app/src/organisms/Desktop/Devices/RunPreview/index.tsx b/app/src/organisms/Desktop/Devices/RunPreview/index.tsx index e3baabba5de..dd6c0e7beab 100644 --- a/app/src/organisms/Desktop/Devices/RunPreview/index.tsx +++ b/app/src/organisms/Desktop/Devices/RunPreview/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useMemo, useState, forwardRef, useRef } from 'react' import { css } from 'styled-components' import { useTranslation } from 'react-i18next' import { ViewportList } from 'react-viewport-list' @@ -28,7 +28,11 @@ import { useMostRecentCompletedAnalysis, useLastRunCommand, } from '/app/resources/runs' -import { CommandText, CommandIcon } from '/app/molecules/Command' +import { + CommandText, + CommandIcon, + getLabwareDefinitionsFromCommands, +} from '/app/molecules/Command' import { Divider } from '/app/atoms/structure' import { NAV_BAR_WIDTH } from '/app/App/constants' @@ -70,19 +74,27 @@ export const RunPreviewComponent = ( } ) const commandsFromQuery = commandsFromQueryResponse?.data - const viewPortRef = React.useRef(null) + const viewPortRef = useRef(null) const currentRunCommandKey = useLastRunCommand(runId, { refetchInterval: LIVE_RUN_COMMANDS_POLL_MS, })?.key const [ isCurrentCommandVisible, setIsCurrentCommandVisible, - ] = React.useState(true) + ] = useState(true) + + const isValidRobotSideAnalysis = robotSideAnalysis != null + const allRunDefs = useMemo( + () => + robotSideAnalysis != null + ? getLabwareDefinitionsFromCommands(robotSideAnalysis.commands) + : [], + [isValidRobotSideAnalysis] + ) if (robotSideAnalysis == null) { return null } - const commands = isRunTerminal ? commandsFromQuery : robotSideAnalysis.commands @@ -196,6 +208,7 @@ export const RunPreviewComponent = ( commandTextData={protocolDataFromAnalysisOrRun} robotType={robotType} color={COLORS.black90} + allRunDefs={allRunDefs} /> @@ -227,4 +240,4 @@ export const RunPreviewComponent = ( ) } -export const RunPreview = React.forwardRef(RunPreviewComponent) +export const RunPreview = forwardRef(RunPreviewComponent) diff --git a/app/src/organisms/Desktop/ProtocolDetails/AnnotatedSteps.tsx b/app/src/organisms/Desktop/ProtocolDetails/AnnotatedSteps.tsx index bdc5848f03b..5af6922afce 100644 --- a/app/src/organisms/Desktop/ProtocolDetails/AnnotatedSteps.tsx +++ b/app/src/organisms/Desktop/ProtocolDetails/AnnotatedSteps.tsx @@ -1,4 +1,6 @@ +import { useMemo } from 'react' import { css } from 'styled-components' + import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' import { ALIGN_CENTER, @@ -11,12 +13,18 @@ import { TYPOGRAPHY, OVERFLOW_AUTO, } from '@opentrons/components' -import { CommandIcon, CommandText } from '/app/molecules/Command' + +import { + CommandIcon, + CommandText, + getLabwareDefinitionsFromCommands, +} from '/app/molecules/Command' import type { CompletedProtocolAnalysis, ProtocolAnalysisOutput, RunTimeCommand, + LabwareDefinition2, } from '@opentrons/shared-data' interface AnnotatedStepsProps { @@ -32,6 +40,15 @@ export function AnnotatedSteps(props: AnnotatedStepsProps): JSX.Element { } ` + const isValidRobotSideAnalysis = analysis != null + const allRunDefs = useMemo( + () => + analysis != null + ? getLabwareDefinitionsFromCommands(analysis.commands) + : [], + [isValidRobotSideAnalysis] + ) + return ( ))} @@ -64,9 +82,15 @@ interface IndividualCommandProps { analysis: ProtocolAnalysisOutput | CompletedProtocolAnalysis stepNumber: string isHighlighted: boolean + allRunDefs: LabwareDefinition2[] } -function IndividualCommand(props: IndividualCommandProps): JSX.Element { - const { command, analysis, stepNumber, isHighlighted } = props +function IndividualCommand({ + command, + analysis, + stepNumber, + isHighlighted, + allRunDefs, +}: IndividualCommandProps): JSX.Element { const backgroundColor = isHighlighted ? COLORS.blue30 : COLORS.grey20 const iconColor = isHighlighted ? COLORS.blue60 : COLORS.grey50 return ( @@ -101,6 +125,7 @@ function IndividualCommand(props: IndividualCommandProps): JSX.Element { robotType={analysis?.robotType ?? FLEX_ROBOT_TYPE} color={COLORS.black90} commandTextData={analysis} + allRunDefs={allRunDefs} /> diff --git a/app/src/organisms/Desktop/ProtocolTimelineScrubber/CommandItem.tsx b/app/src/organisms/Desktop/ProtocolTimelineScrubber/CommandItem.tsx index ae9377a19a0..573893f096f 100644 --- a/app/src/organisms/Desktop/ProtocolTimelineScrubber/CommandItem.tsx +++ b/app/src/organisms/Desktop/ProtocolTimelineScrubber/CommandItem.tsx @@ -14,6 +14,7 @@ import { COMMAND_WIDTH_PX } from './index' import type { CompletedProtocolAnalysis, + LabwareDefinition2, ProtocolAnalysisOutput, RobotType, RunTimeCommand, @@ -26,17 +27,18 @@ interface CommandItemProps { setCurrentCommandIndex: (index: number) => void analysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput robotType: RobotType + allRunDefs: LabwareDefinition2[] } -export function CommandItem(props: CommandItemProps): JSX.Element { +export function CommandItem({ + index, + command, + currentCommandIndex, + setCurrentCommandIndex, + analysis, + robotType, + allRunDefs, +}: CommandItemProps): JSX.Element { const [showDetails, setShowDetails] = useState(false) - const { - index, - command, - currentCommandIndex, - setCurrentCommandIndex, - analysis, - robotType, - } = props const params: RunTimeCommand['params'] = command.params ?? {} return ( {showDetails ? Object.entries(params).map(([key, value]) => ( diff --git a/app/src/organisms/Desktop/ProtocolTimelineScrubber/index.tsx b/app/src/organisms/Desktop/ProtocolTimelineScrubber/index.tsx index 619928fcd4b..d92bc62e695 100644 --- a/app/src/organisms/Desktop/ProtocolTimelineScrubber/index.tsx +++ b/app/src/organisms/Desktop/ProtocolTimelineScrubber/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useMemo, useState, useEffect, useRef } from 'react' import map from 'lodash/map' import reduce from 'lodash/reduce' import ViewportList from 'react-viewport-list' @@ -27,7 +27,9 @@ import { wellFillFromWellContents, } from './utils' import { CommandItem } from './CommandItem' +import { getLabwareDefinitionsFromCommands } from '/app/molecules/Command' +import type { ComponentProps } from 'react' import type { ViewportListRef } from 'react-viewport-list' import type { CompletedProtocolAnalysis, @@ -66,12 +68,10 @@ export function ProtocolTimelineScrubber( props: ProtocolTimelineScrubberProps ): JSX.Element { const { commands, analysis, robotType = FLEX_ROBOT_TYPE } = props - const wrapperRef = React.useRef(null) - const commandListRef = React.useRef(null) - const [currentCommandIndex, setCurrentCommandIndex] = React.useState( - 0 - ) - const [isPlaying, setIsPlaying] = React.useState(true) + const wrapperRef = useRef(null) + const commandListRef = useRef(null) + const [currentCommandIndex, setCurrentCommandIndex] = useState(0) + const [isPlaying, setIsPlaying] = useState(true) const currentCommandsSlice = commands.slice(0, currentCommandIndex + 1) const { frame, invariantContext } = getResultingTimelineFrameFromRunCommands( @@ -81,7 +81,7 @@ export function ProtocolTimelineScrubber( setIsPlaying(!isPlaying) } - React.useEffect(() => { + useEffect(() => { if (isPlaying) { const intervalId = setInterval(() => { setCurrentCommandIndex(prev => { @@ -123,6 +123,15 @@ export function ProtocolTimelineScrubber( liquid => liquid.displayColor ?? COLORS.blue50 ) + const isValidRobotSideAnalysis = analysis != null + const allRunDefs = useMemo( + () => + analysis != null + ? getLabwareDefinitionsFromCommands(analysis.commands) + : [], + [isValidRobotSideAnalysis] + ) + return ( ['innerProps'] => { + ): ComponentProps['innerProps'] => { if (moduleState.type === THERMOCYCLER_MODULE_TYPE) { let lidMotorState = 'unknown' if (moduleState.lidOpen === true) { @@ -282,6 +291,7 @@ export function ProtocolTimelineScrubber( setCurrentCommandIndex={setCurrentCommandIndex} analysis={analysis} robotType={robotType} + allRunDefs={allRunDefs} /> )} diff --git a/app/src/organisms/Desktop/RunProgressMeter/hooks/useRunProgressCopy.tsx b/app/src/organisms/Desktop/RunProgressMeter/hooks/useRunProgressCopy.tsx index 66e1960f939..e9db153498b 100644 --- a/app/src/organisms/Desktop/RunProgressMeter/hooks/useRunProgressCopy.tsx +++ b/app/src/organisms/Desktop/RunProgressMeter/hooks/useRunProgressCopy.tsx @@ -1,3 +1,5 @@ +import { useMemo } from 'react' + import { RUN_STATUS_BLOCKED_BY_OPEN_DOOR, RUN_STATUS_IDLE, @@ -6,7 +8,10 @@ import type * as React from 'react' import { useTranslation } from 'react-i18next' import { getCommandTextData } from '/app/molecules/Command/utils/getCommandTextData' import { LegacyStyledText } from '@opentrons/components' -import { CommandText } from '/app/molecules/Command' +import { + CommandText, + getLabwareDefinitionsFromCommands, +} from '/app/molecules/Command' import { TERMINAL_RUN_STATUSES } from '../constants' import type { CommandDetail, RunStatus } from '@opentrons/api-client' @@ -52,6 +57,15 @@ export function useRunProgressCopy({ runStatus === RUN_STATUS_BLOCKED_BY_OPEN_DOOR) || runStatus === RUN_STATUS_IDLE + const isValidRobotSideAnalysis = analysis != null + const allRunDefs = useMemo( + () => + analysis != null + ? getLabwareDefinitionsFromCommands(analysis.commands) + : [], + [isValidRobotSideAnalysis] + ) + const currentStepContents = ((): JSX.Element | null => { if (runHasNotBeenStarted) { return {t('not_started_yet')} @@ -61,6 +75,7 @@ export function useRunProgressCopy({ commandTextData={getCommandTextData(analysis)} command={analysisCommands[(currentStepNumber as number) - 1]} robotType={robotType} + allRunDefs={allRunDefs} /> ) } else if ( @@ -73,6 +88,7 @@ export function useRunProgressCopy({ commandTextData={getCommandTextData(analysis)} command={runCommandDetails.data} robotType={robotType} + allRunDefs={allRunDefs} /> ) } else { diff --git a/app/src/organisms/ErrorRecoveryFlows/ErrorRecoveryWizard.tsx b/app/src/organisms/ErrorRecoveryFlows/ErrorRecoveryWizard.tsx index b42ffa9c9cd..cfe211f7f3e 100644 --- a/app/src/organisms/ErrorRecoveryFlows/ErrorRecoveryWizard.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/ErrorRecoveryWizard.tsx @@ -29,7 +29,7 @@ import { RecoveryInProgress } from './RecoveryInProgress' import { getErrorKind } from './utils' import { RECOVERY_MAP } from './constants' -import type { RobotType } from '@opentrons/shared-data' +import type { LabwareDefinition2, RobotType } from '@opentrons/shared-data' import type { RecoveryRoute, RouteStep, RecoveryContentProps } from './types' import type { ERUtilsResults, useRetainedFailedCommandBySource } from './hooks' import type { ErrorRecoveryFlowsProps } from '.' @@ -68,6 +68,7 @@ export type ErrorRecoveryWizardProps = ErrorRecoveryFlowsProps & isOnDevice: boolean analytics: UseRecoveryAnalyticsResult failedCommand: ReturnType + allRunDefs: LabwareDefinition2[] } export function ErrorRecoveryWizard( diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoverySplash.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoverySplash.tsx index 01cf1faa65b..aad0f670cd0 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoverySplash.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoverySplash.tsx @@ -37,7 +37,7 @@ import { import { RecoveryInterventionModal, StepInfo } from './shared' import { useToaster } from '../ToasterOven' -import type { RobotType } from '@opentrons/shared-data' +import type { LabwareDefinition2, RobotType } from '@opentrons/shared-data' import type { ErrorRecoveryFlowsProps } from '.' import type { ERUtilsResults, @@ -70,6 +70,7 @@ type RecoverySplashProps = ErrorRecoveryFlowsProps & resumePausedRecovery: boolean toggleERWizAsActiveUser: UseRecoveryTakeoverResult['toggleERWizAsActiveUser'] analytics: UseRecoveryAnalyticsResult + allRunDefs: LabwareDefinition2[] } export function RecoverySplash(props: RecoverySplashProps): JSX.Element | null { const { diff --git a/app/src/organisms/ErrorRecoveryFlows/__fixtures__/index.ts b/app/src/organisms/ErrorRecoveryFlows/__fixtures__/index.ts index d2efa1cc218..c79e270bbed 100644 --- a/app/src/organisms/ErrorRecoveryFlows/__fixtures__/index.ts +++ b/app/src/organisms/ErrorRecoveryFlows/__fixtures__/index.ts @@ -92,4 +92,5 @@ export const mockRecoveryContentProps: RecoveryContentProps = { reportActionSelectedEvent: () => {}, reportActionSelectedResult: () => {}, }, + allRunDefs: [], } diff --git a/app/src/organisms/ErrorRecoveryFlows/__tests__/ErrorRecoveryFlows.test.tsx b/app/src/organisms/ErrorRecoveryFlows/__tests__/ErrorRecoveryFlows.test.tsx index f15eedefc2d..b4fda69bd13 100644 --- a/app/src/organisms/ErrorRecoveryFlows/__tests__/ErrorRecoveryFlows.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/__tests__/ErrorRecoveryFlows.test.tsx @@ -8,6 +8,7 @@ import { RUN_STATUS_RUNNING, RUN_STATUS_STOP_REQUESTED, } from '@opentrons/api-client' +import { getLabwareDefinitionsFromCommands } from '/app/molecules/Command' import { renderWithProviders } from '/app/__testing-utils__' import { i18n } from '/app/i18n' @@ -32,6 +33,7 @@ vi.mock('/app/redux/config') vi.mock('../RecoverySplash') vi.mock('/app/redux-resources/analytics') vi.mock('@opentrons/react-api-client') +vi.mock('/app/molecules/Command') vi.mock('react-redux', async () => { const actual = await vi.importActual('react-redux') return { @@ -43,6 +45,7 @@ vi.mock('react-redux', async () => { describe('useErrorRecoveryFlows', () => { beforeEach(() => { vi.mocked(useCurrentlyRecoveringFrom).mockReturnValue('mockCommand' as any) + vi.mocked(getLabwareDefinitionsFromCommands).mockReturnValue([]) }) it('should have initial state of isERActive as false', () => { diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryToasts.test.tsx b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryToasts.test.tsx index 08c51478491..7c6b3b74065 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryToasts.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryToasts.test.tsx @@ -34,6 +34,7 @@ const DEFAULT_PROPS: BuildToast = { selectedRecoveryOption: RECOVERY_MAP.RETRY_SAME_TIPS.ROUTE, commandTextData: { commands: [] } as any, robotType: FLEX_ROBOT_TYPE, + allRunDefs: [], } // Utility function for rendering with I18nextProvider @@ -208,6 +209,7 @@ describe('useRecoveryFullCommandText', () => { robotType: FLEX_ROBOT_TYPE, stepNumber: 0, commandTextData: { commands: [TEST_COMMAND] } as any, + allRunDefs: [], }) ) @@ -225,6 +227,7 @@ describe('useRecoveryFullCommandText', () => { robotType: FLEX_ROBOT_TYPE, stepNumber: 1, commandTextData: { commands: [] } as any, + allRunDefs: [], }) ) @@ -237,6 +240,7 @@ describe('useRecoveryFullCommandText', () => { robotType: FLEX_ROBOT_TYPE, stepNumber: '?', commandTextData: { commands: [] } as any, + allRunDefs: [], }) ) @@ -257,6 +261,7 @@ describe('useRecoveryFullCommandText', () => { commandTextData: { commands: [TC_COMMAND], } as any, + allRunDefs: [], }) ) expect(result.current).toBe('tc starting profile of 1231231 element steps') @@ -276,6 +281,7 @@ describe('useRecoveryFullCommandText', () => { commandTextData: { commands: [TC_COMMAND], } as any, + allRunDefs: [], }) ) expect(result.current).toBe('tc starting profile of 1231231 element steps') diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts index 38af12de4a7..155c534ba6f 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts @@ -20,7 +20,7 @@ import { useShowDoorInfo } from './useShowDoorInfo' import { useCleanupRecoveryState } from './useCleanupRecoveryState' import { useFailedPipetteUtils } from './useFailedPipetteUtils' -import type { RobotType } from '@opentrons/shared-data' +import type { LabwareDefinition2, RobotType } from '@opentrons/shared-data' import type { IRecoveryMap, RouteStep, RecoveryRoute } from '../types' import type { ErrorRecoveryFlowsProps } from '..' import type { UseRouteUpdateActionsResult } from './useRouteUpdateActions' @@ -47,6 +47,7 @@ export type ERUtilsProps = Omit & { robotType: RobotType failedCommand: ReturnType showTakeover: boolean + allRunDefs: LabwareDefinition2[] } export interface ERUtilsResults { @@ -80,6 +81,7 @@ export function useERUtils({ robotType, runStatus, showTakeover, + allRunDefs, }: ERUtilsProps): ERUtilsResults { const { data: attachedInstruments } = useInstrumentsQuery() const { data: runRecord } = useNotifyRunQuery(runId) @@ -113,6 +115,7 @@ export function useERUtils({ isOnDevice, commandTextData: protocolAnalysis, robotType, + allRunDefs, }) const failedPipetteUtils = useFailedPipetteUtils({ diff --git a/app/src/organisms/ErrorRecoveryFlows/index.tsx b/app/src/organisms/ErrorRecoveryFlows/index.tsx index fce380ced9f..a447df2dafe 100644 --- a/app/src/organisms/ErrorRecoveryFlows/index.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/index.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useMemo, useState } from 'react' import { useSelector } from 'react-redux' import { @@ -30,6 +30,7 @@ import { import type { RunStatus } from '@opentrons/api-client' import type { CompletedProtocolAnalysis } from '@opentrons/shared-data' import type { FailedCommand } from './types' +import { getLabwareDefinitionsFromCommands } from '/app/molecules/Command' const VALID_ER_RUN_STATUSES: RunStatus[] = [ RUN_STATUS_AWAITING_RECOVERY, @@ -125,6 +126,15 @@ export function ErrorRecoveryFlows( const robotType = protocolAnalysis?.robotType ?? OT2_ROBOT_TYPE const robotName = useHost()?.robotName ?? 'robot' + const isValidRobotSideAnalysis = protocolAnalysis != null + const allRunDefs = useMemo( + () => + protocolAnalysis != null + ? getLabwareDefinitionsFromCommands(protocolAnalysis.commands) + : [], + [isValidRobotSideAnalysis] + ) + const { showTakeover, isActiveUser, @@ -140,6 +150,7 @@ export function ErrorRecoveryFlows( robotType, showTakeover, failedCommand: failedCommandBySource, + allRunDefs, }) const renderWizard = @@ -164,6 +175,7 @@ export function ErrorRecoveryFlows( robotType={robotType} isOnDevice={isOnDevice} failedCommand={failedCommandBySource} + allRunDefs={allRunDefs} /> ) : null} {showSplash ? ( @@ -176,6 +188,7 @@ export function ErrorRecoveryFlows( toggleERWizAsActiveUser={toggleERWizAsActiveUser} failedCommand={failedCommandBySource} resumePausedRecovery={!renderWizard && !showTakeover} + allRunDefs={allRunDefs} /> ) : null} diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/ErrorDetailsModal.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/ErrorDetailsModal.tsx index 27b5b596c37..b988c83971b 100644 --- a/app/src/organisms/ErrorRecoveryFlows/shared/ErrorDetailsModal.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/shared/ErrorDetailsModal.tsx @@ -22,7 +22,7 @@ import { InlineNotification } from '/app/atoms/InlineNotification' import { StepInfo } from './StepInfo' import { getErrorKind } from '../utils' -import type { RobotType } from '@opentrons/shared-data' +import type { LabwareDefinition2, RobotType } from '@opentrons/shared-data' import type { IconProps } from '@opentrons/components' import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' import type { ERUtilsResults, useRetainedFailedCommandBySource } from '../hooks' @@ -52,6 +52,7 @@ type ErrorDetailsModalProps = Omit< robotType: RobotType desktopType: DesktopSizeType failedCommand: ReturnType + allRunDefs: LabwareDefinition2[] } export function ErrorDetailsModal(props: ErrorDetailsModalProps): JSX.Element { diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/FailedStepNextStep.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/FailedStepNextStep.tsx index bad7b536dfe..a27a1adea04 100644 --- a/app/src/organisms/ErrorRecoveryFlows/shared/FailedStepNextStep.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/shared/FailedStepNextStep.tsx @@ -8,6 +8,7 @@ export function FailedStepNextStep({ commandsAfterFailedCommand, protocolAnalysis, robotType, + allRunDefs, }: Pick< RecoveryContentProps, | 'stepCounts' @@ -15,6 +16,7 @@ export function FailedStepNextStep({ | 'commandsAfterFailedCommand' | 'protocolAnalysis' | 'robotType' + | 'allRunDefs' >): JSX.Element { const { t } = useTranslation('error_recovery') const failedCommandByAnalysis = failedCommand?.byAnalysis ?? null @@ -44,6 +46,7 @@ export function FailedStepNextStep({ return ( ['desktopStyle'] oddStyle?: React.ComponentProps['oddStyle'] } @@ -25,6 +26,7 @@ export function StepInfo({ failedCommand, robotType, protocolAnalysis, + allRunDefs, ...styleProps }: StepInfoProps): JSX.Element { const { t } = useTranslation('error_recovery') @@ -54,6 +56,7 @@ export function StepInfo({ modernStyledTextDefaults={true} desktopStyle={desktopStyleDefaulted} oddStyle={oddStyleDefaulted} + allRunDefs={allRunDefs} /> ) : null} diff --git a/app/src/organisms/ODD/RunningProtocol/CurrentRunningProtocolCommand.tsx b/app/src/organisms/ODD/RunningProtocol/CurrentRunningProtocolCommand.tsx index e474d58ab11..f87f7cd71e9 100644 --- a/app/src/organisms/ODD/RunningProtocol/CurrentRunningProtocolCommand.tsx +++ b/app/src/organisms/ODD/RunningProtocol/CurrentRunningProtocolCommand.tsx @@ -30,6 +30,7 @@ import { useNotifyAllCommandsQuery } from '/app/resources/runs' import type { CompletedProtocolAnalysis, + LabwareDefinition2, RobotType, RunTimeCommand, } from '@opentrons/shared-data' @@ -123,6 +124,7 @@ interface CurrentRunningProtocolCommandProps { lastAnimatedCommand: string | null lastRunCommand: RunCommandSummary | null updateLastAnimatedCommand: (newCommandKey: string) => void + allRunDefs: LabwareDefinition2[] protocolName?: string currentRunCommandIndex?: number } @@ -143,6 +145,7 @@ export function CurrentRunningProtocolCommand({ lastRunCommand, lastAnimatedCommand, updateLastAnimatedCommand, + allRunDefs, }: CurrentRunningProtocolCommandProps): JSX.Element | null { const { t } = useTranslation('run_details') const { data: mostRecentCommandData } = useNotifyAllCommandsQuery(runId, { @@ -261,6 +264,7 @@ export function CurrentRunningProtocolCommand({ commandTextData={getCommandTextData(robotSideAnalysis)} robotType={robotType} isOnDevice={true} + allRunDefs={allRunDefs} /> ) : null} diff --git a/app/src/organisms/ODD/RunningProtocol/RunningProtocolCommandList.tsx b/app/src/organisms/ODD/RunningProtocol/RunningProtocolCommandList.tsx index f161fa77e05..3e928ed88b4 100644 --- a/app/src/organisms/ODD/RunningProtocol/RunningProtocolCommandList.tsx +++ b/app/src/organisms/ODD/RunningProtocol/RunningProtocolCommandList.tsx @@ -31,6 +31,7 @@ import { ANALYTICS_PROTOCOL_RUN_ACTION } from '/app/redux/analytics' import type { ViewportListRef } from 'react-viewport-list' import type { CompletedProtocolAnalysis, + LabwareDefinition2, RobotType, } from '@opentrons/shared-data' import type { RunStatus } from '@opentrons/api-client' @@ -60,17 +61,6 @@ const COMMAND_ROW_STYLE = css` overflow: hidden; ` -// Note (kj:05/15/2023) -// This blur part will be fixed before the launch -// const BOTTOM_ROW_STYLE = css` -// position: ${POSITION_ABSOLUTE}; -// bottom: 0; -// width: 100%; -// height: 5rem; -// z-index: 6; -// backdrop-filter: blur(1.5px); -// ` - interface VisibleIndexRange { lowestVisibleIndex: number highestVisibleIndex: number @@ -87,6 +77,7 @@ interface RunningProtocolCommandListProps { robotAnalyticsData: RobotAnalyticsData | null protocolName?: string currentRunCommandIndex?: number + allRunDefs: LabwareDefinition2[] } export function RunningProtocolCommandList({ @@ -100,6 +91,7 @@ export function RunningProtocolCommandList({ robotAnalyticsData, protocolName, currentRunCommandIndex, + allRunDefs, }: RunningProtocolCommandListProps): JSX.Element { const { t } = useTranslation('run_details') const viewPortRef = useRef(null) @@ -249,6 +241,7 @@ export function RunningProtocolCommandList({ robotType={robotType} css={COMMAND_ROW_STYLE} isOnDevice={true} + allRunDefs={allRunDefs} /> diff --git a/app/src/organisms/ODD/RunningProtocol/__tests__/CurrentRunningProtocolCommand.test.tsx b/app/src/organisms/ODD/RunningProtocol/__tests__/CurrentRunningProtocolCommand.test.tsx index 8e19bb69491..54d241ff9af 100644 --- a/app/src/organisms/ODD/RunningProtocol/__tests__/CurrentRunningProtocolCommand.test.tsx +++ b/app/src/organisms/ODD/RunningProtocol/__tests__/CurrentRunningProtocolCommand.test.tsx @@ -55,6 +55,7 @@ describe('CurrentRunningProtocolCommand', () => { updateLastAnimatedCommand: mockUpdateLastAnimatedCommand, robotType: FLEX_ROBOT_TYPE, runId: 'MOCK_RUN_ID', + allRunDefs: [], } vi.mocked(useNotifyAllCommandsQuery).mockReturnValue({} as any) diff --git a/app/src/organisms/ODD/RunningProtocol/__tests__/RunningProtocolCommandList.test.tsx b/app/src/organisms/ODD/RunningProtocol/__tests__/RunningProtocolCommandList.test.tsx index f506dce8404..199ae940c3b 100644 --- a/app/src/organisms/ODD/RunningProtocol/__tests__/RunningProtocolCommandList.test.tsx +++ b/app/src/organisms/ODD/RunningProtocol/__tests__/RunningProtocolCommandList.test.tsx @@ -36,6 +36,7 @@ describe('RunningProtocolCommandList', () => { protocolName: 'mockRunningProtocolName', currentRunCommandIndex: 0, robotType: FLEX_ROBOT_TYPE, + allRunDefs: [], } }) it('should render text and buttons', () => { diff --git a/app/src/pages/ODD/RunningProtocol/index.tsx b/app/src/pages/ODD/RunningProtocol/index.tsx index bc87e0e4934..b75284386b8 100644 --- a/app/src/pages/ODD/RunningProtocol/index.tsx +++ b/app/src/pages/ODD/RunningProtocol/index.tsx @@ -1,4 +1,4 @@ -import { useState, useRef, useEffect } from 'react' +import { useState, useRef, useEffect, useMemo } from 'react' import { useParams } from 'react-router-dom' import styled, { css } from 'styled-components' import { useSelector } from 'react-redux' @@ -57,6 +57,7 @@ import { useErrorRecoveryFlows, ErrorRecoveryFlows, } from '/app/organisms/ErrorRecoveryFlows' +import { getLabwareDefinitionsFromCommands } from '/app/molecules/Command' import type { OnDeviceRouteParams } from '/app/App/types' @@ -152,6 +153,15 @@ export function RunningProtocol(): JSX.Element { } }, [currentOption, swipeType, setSwipeType]) + const isValidRobotSideAnalysis = robotSideAnalysis != null + const allRunDefs = useMemo( + () => + robotSideAnalysis != null + ? getLabwareDefinitionsFromCommands(robotSideAnalysis.commands) + : [], + [isValidRobotSideAnalysis] + ) + return ( <> {isERActive ? ( @@ -228,6 +238,7 @@ export function RunningProtocol(): JSX.Element { updateLastAnimatedCommand={(newCommandKey: string) => (lastAnimatedCommand.current = newCommandKey) } + allRunDefs={allRunDefs} /> ) : ( <> @@ -242,6 +253,7 @@ export function RunningProtocol(): JSX.Element { robotAnalyticsData={robotAnalyticsData} currentRunCommandIndex={currentRunCommandIndex} robotSideAnalysis={robotSideAnalysis} + allRunDefs={allRunDefs} />