diff --git a/app/src/molecules/DeckThumbnail/__tests__/DeckThumbnail.test.tsx b/app/src/molecules/DeckThumbnail/__tests__/DeckThumbnail.test.tsx index a9cbf8e909b..d1d5fe35282 100644 --- a/app/src/molecules/DeckThumbnail/__tests__/DeckThumbnail.test.tsx +++ b/app/src/molecules/DeckThumbnail/__tests__/DeckThumbnail.test.tsx @@ -1,50 +1,108 @@ import * as React from 'react' import { when, resetAllWhenMocks } from 'jest-when' import { - getRobotTypeFromLoadedLabware, + FLEX_ROBOT_TYPE, getDeckDefFromRobotType, + getRobotTypeFromLoadedLabware, + OT2_ROBOT_TYPE, } from '@opentrons/shared-data' import ot2StandardDeckDef from '@opentrons/shared-data/deck/definitions/3/ot2_standard.json' -import { renderWithProviders } from '@opentrons/components' -import { simpleAnalysisFileFixture } from '@opentrons/api-client' +import ot3StandardDeckDef from '@opentrons/shared-data/deck/definitions/3/ot3_standard.json' +import fixture_tiprack_300_ul from '@opentrons/shared-data/labware/fixtures/2/fixture_tiprack_300_ul.json' +import { + BaseDeck, + EXTENDED_DECK_CONFIG_FIXTURE, + partialComponentPropsMatcher, + renderWithProviders, +} from '@opentrons/components' +import { + parseInitialLoadedLabwareByAdapter, + parseLabwareInfoByLiquidId, + simpleAnalysisFileFixture, +} from '@opentrons/api-client' + import { i18n } from '../../../i18n' +import { useAttachedModules } from '../../../organisms/Devices/hooks' +import { getStandardDeckViewLayerBlockList } from '../utils/getStandardDeckViewLayerBlockList' +import { getDeckConfigFromProtocolCommands } from '../../../resources/deck_configuration/utils' +import { getAttachedProtocolModuleMatches } from '../../../organisms/ProtocolSetupModulesAndDeck/utils' +import { getProtocolModulesInfo } from '../../../organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo' +import { getLabwareRenderInfo } from '../../../organisms/Devices/ProtocolRun/utils/getLabwareRenderInfo' +import { mockProtocolModuleInfo } from '../../../organisms/ProtocolSetupLabware/__fixtures__' +import { mockFetchModulesSuccessActionPayloadModules } from '../../../redux/modules/__fixtures__' import { DeckThumbnail } from '../' -import type { LoadedLabware, RunTimeCommand } from '@opentrons/shared-data' -jest.mock('@opentrons/shared-data', () => { - const actualSharedData = jest.requireActual('@opentrons/shared-data') - return { - ...actualSharedData, - getRobotTypeFromLoadedLabware: jest.fn(), - getDeckDefFromRobotType: jest.fn(), - } -}) -jest.mock('@opentrons/components', () => { - const actualComponents = jest.requireActual('@opentrons/components') - return { - ...actualComponents, - Module: jest.fn(({ def, x, y, children }) => ( -
- mock Module ({x},{y}) {def.model} {children} -
- )), - LabwareRender: jest.fn(({ definition }) => ( -
mock LabwareRender {definition.parameters.loadName}
- )), - } -}) +import type { + LabwareDefinition2, + LoadedLabware, + ModuleModel, + ModuleType, + RunTimeCommand, +} from '@opentrons/shared-data' + +jest.mock('@opentrons/components/src/hardware-sim/BaseDeck') +jest.mock('@opentrons/api-client') +jest.mock('@opentrons/shared-data/js/helpers') jest.mock('../../../redux/config') +jest.mock('../../../resources/deck_configuration/utils') +jest.mock('../../../organisms/ProtocolSetupModulesAndDeck/utils') +jest.mock('../../../organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo') +jest.mock('../../../organisms/Devices/hooks') +jest.mock('../../../organisms/Devices/ProtocolRun/utils/getLabwareRenderInfo') -const mockgetRobotTypeFromLoadedLabware = getRobotTypeFromLoadedLabware as jest.MockedFunction< +const mockGetRobotTypeFromLoadedLabware = getRobotTypeFromLoadedLabware as jest.MockedFunction< typeof getRobotTypeFromLoadedLabware > -const mockgetDeckDefFromRobotType = getDeckDefFromRobotType as jest.MockedFunction< +const mockGetDeckDefFromRobotType = getDeckDefFromRobotType as jest.MockedFunction< typeof getDeckDefFromRobotType > +const mockParseInitialLoadedLabwareByAdapter = parseInitialLoadedLabwareByAdapter as jest.MockedFunction< + typeof parseInitialLoadedLabwareByAdapter +> +const mockParseLabwareInfoByLiquidId = parseLabwareInfoByLiquidId as jest.MockedFunction< + typeof parseLabwareInfoByLiquidId +> +const mockUseAttachedModules = useAttachedModules as jest.MockedFunction< + typeof useAttachedModules +> +const mockGetDeckConfigFromProtocolCommands = getDeckConfigFromProtocolCommands as jest.MockedFunction< + typeof getDeckConfigFromProtocolCommands +> +const mockGetLabwareRenderInfo = getLabwareRenderInfo as jest.MockedFunction< + typeof getLabwareRenderInfo +> +const mockGetProtocolModulesInfo = getProtocolModulesInfo as jest.MockedFunction< + typeof getProtocolModulesInfo +> +const mockGetAttachedProtocolModuleMatches = getAttachedProtocolModuleMatches as jest.MockedFunction< + typeof getAttachedProtocolModuleMatches +> +const mockBaseDeck = BaseDeck as jest.MockedFunction +const protocolAnalysis = simpleAnalysisFileFixture as any const commands: RunTimeCommand[] = simpleAnalysisFileFixture.commands as any const labware: LoadedLabware[] = simpleAnalysisFileFixture.labware as any +const MOCK_300_UL_TIPRACK_ID = '300_ul_tiprack_id' +const MOCK_MAGNETIC_MODULE_COORDS = [10, 20, 0] +const MOCK_SECOND_MAGNETIC_MODULE_COORDS = [100, 200, 0] +const MOCK_300_UL_TIPRACK_COORDS = [30, 40, 0] +const mockMagneticModule = { + moduleId: 'someMagneticModule', + model: 'magneticModuleV2' as ModuleModel, + type: 'magneticModuleType' as ModuleType, + labwareOffset: { x: 5, y: 5, z: 5 }, + cornerOffsetFromSlot: { x: 1, y: 1, z: 1 }, + dimensions: { + xDimension: 100, + yDimension: 100, + footprintXDimension: 50, + footprintYDimension: 50, + labwareInterfaceXDimension: 80, + labwareInterfaceYDimension: 120, + }, + twoDimensionalRendering: { children: [] }, +} const render = (props: React.ComponentProps) => { return renderWithProviders(, { @@ -54,47 +112,184 @@ const render = (props: React.ComponentProps) => { describe('DeckThumbnail', () => { beforeEach(() => { - when(mockgetRobotTypeFromLoadedLabware) + when(mockGetRobotTypeFromLoadedLabware) .calledWith(labware) - .mockReturnValue('OT-2 Standard') - when(mockgetDeckDefFromRobotType) - .calledWith('OT-2 Standard') + .mockReturnValue(OT2_ROBOT_TYPE) + when(mockGetDeckDefFromRobotType) + .calledWith(OT2_ROBOT_TYPE) .mockReturnValue(ot2StandardDeckDef as any) + when(mockParseInitialLoadedLabwareByAdapter) + .calledWith(commands) + .mockReturnValue({}) + when(mockParseLabwareInfoByLiquidId) + .calledWith(commands) + .mockReturnValue({}) + mockUseAttachedModules.mockReturnValue( + mockFetchModulesSuccessActionPayloadModules + ) + when(mockGetDeckConfigFromProtocolCommands) + .calledWith(commands) + .mockReturnValue(EXTENDED_DECK_CONFIG_FIXTURE) + when(mockGetLabwareRenderInfo).mockReturnValue({}) + when(mockGetProtocolModulesInfo) + .calledWith(protocolAnalysis, ot2StandardDeckDef as any) + .mockReturnValue(mockProtocolModuleInfo) + when(mockGetAttachedProtocolModuleMatches) + .calledWith( + mockFetchModulesSuccessActionPayloadModules, + mockProtocolModuleInfo + ) + .mockReturnValue([ + { + moduleId: mockMagneticModule.moduleId, + x: MOCK_MAGNETIC_MODULE_COORDS[0], + y: MOCK_MAGNETIC_MODULE_COORDS[1], + z: MOCK_MAGNETIC_MODULE_COORDS[2], + moduleDef: mockMagneticModule as any, + nestedLabwareDef: null, + nestedLabwareDisplayName: null, + nestedLabwareId: null, + slotName: '1', + protocolLoadOrder: 1, + attachedModuleMatch: null, + }, + { + moduleId: mockMagneticModule.moduleId, + x: MOCK_SECOND_MAGNETIC_MODULE_COORDS[0], + y: MOCK_SECOND_MAGNETIC_MODULE_COORDS[1], + z: MOCK_SECOND_MAGNETIC_MODULE_COORDS[2], + moduleDef: mockMagneticModule as any, + nestedLabwareDef: null, + nestedLabwareDisplayName: null, + nestedLabwareId: null, + slotName: '2', + protocolLoadOrder: 0, + attachedModuleMatch: null, + }, + ]) + when(mockBaseDeck) + .calledWith( + partialComponentPropsMatcher({ + robotType: OT2_ROBOT_TYPE, + deckLayerBlocklist: getStandardDeckViewLayerBlockList(OT2_ROBOT_TYPE), + }) + ) + .mockReturnValue(
mock BaseDeck
) }) afterEach(() => { resetAllWhenMocks() + jest.clearAllMocks() }) - it('renders loaded equipment from protocol analysis file', () => { - const { queryByText } = render({ commands, labware }) - expect(queryByText('mock Module (0,0) magneticModuleV2')).not.toBeFalsy() - expect( - queryByText('mock Module (265,0) temperatureModuleV2') - ).not.toBeFalsy() - expect( - queryByText('mock LabwareRender opentrons_96_tiprack_300ul') - ).not.toBeFalsy() - expect( - queryByText( - 'mock LabwareRender opentrons_24_aluminumblock_generic_2ml_screwcap' - ) - ).not.toBeFalsy() - expect( - queryByText('mock LabwareRender nest_96_wellplate_100ul_pcr_full_skirt') - ).not.toBeFalsy() - }) it('renders an OT-2 deck view when the protocol is an OT-2 protocol', () => { - when(mockgetRobotTypeFromLoadedLabware) - .calledWith(labware) - .mockReturnValue('OT-2 Standard') - render({ commands, labware }) - expect(mockgetDeckDefFromRobotType).toHaveBeenCalledWith('OT-2 Standard') + const { getByText } = render({ + protocolAnalysis: protocolAnalysis, + }) + getByText('mock BaseDeck') }) + it('renders an OT-3 deck view when the protocol is an OT-3 protocol', () => { - when(mockgetRobotTypeFromLoadedLabware) + // ToDo (kk:11/06/2023) update this test later + // const mockLabwareLocations = [ + // { + // labwareLocation: { slotName: 'C1' }, + // definition: fixture_tiprack_300_ul as LabwareDefinition2, + // topLabwareId: '300_ul_tiprack_id', + // topLabwareDisplayName: 'fresh tips', + // }, + // ] + // const mockModuleLocations = [ + // { + // moduleModel: 'magneticModuleV2', + // moduleLocation: { slotName: 'C1' }, + // innerProps: {}, + // nestedLabwareDef: null, + // }, + // { + // moduleModel: 'magneticModuleV2', + // moduleLocation: { slotName: 'B1' }, + // innerProps: {}, + // nestedLabwareDef: null, + // }, + // ] + when(mockGetRobotTypeFromLoadedLabware) .calledWith(labware) - .mockReturnValue('OT-3 Standard') - render({ commands, labware }) - expect(mockgetDeckDefFromRobotType).toHaveBeenCalledWith('OT-3 Standard') + .mockReturnValue(FLEX_ROBOT_TYPE) + when(mockGetDeckDefFromRobotType) + .calledWith(FLEX_ROBOT_TYPE) + .mockReturnValue(ot3StandardDeckDef as any) + when(mockParseInitialLoadedLabwareByAdapter) + .calledWith(commands) + .mockReturnValue({}) + mockUseAttachedModules.mockReturnValue( + mockFetchModulesSuccessActionPayloadModules + ) + when(mockGetDeckConfigFromProtocolCommands) + .calledWith(commands) + .mockReturnValue(EXTENDED_DECK_CONFIG_FIXTURE) + when(mockGetLabwareRenderInfo).mockReturnValue({ + [MOCK_300_UL_TIPRACK_ID]: { + labwareDef: fixture_tiprack_300_ul as LabwareDefinition2, + displayName: 'fresh tips', + x: MOCK_300_UL_TIPRACK_COORDS[0], + y: MOCK_300_UL_TIPRACK_COORDS[1], + z: MOCK_300_UL_TIPRACK_COORDS[2], + slotName: 'C1', + }, + }) + when(mockGetProtocolModulesInfo) + .calledWith(protocolAnalysis, ot3StandardDeckDef as any) + .mockReturnValue(mockProtocolModuleInfo) + when(mockGetAttachedProtocolModuleMatches) + .calledWith( + mockFetchModulesSuccessActionPayloadModules, + mockProtocolModuleInfo + ) + .mockReturnValue([ + { + moduleId: mockMagneticModule.moduleId, + x: MOCK_MAGNETIC_MODULE_COORDS[0], + y: MOCK_MAGNETIC_MODULE_COORDS[1], + z: MOCK_MAGNETIC_MODULE_COORDS[2], + moduleDef: mockMagneticModule as any, + nestedLabwareDef: null, + nestedLabwareDisplayName: null, + nestedLabwareId: null, + slotName: 'C1', + protocolLoadOrder: 1, + attachedModuleMatch: null, + }, + { + moduleId: mockMagneticModule.moduleId, + x: MOCK_SECOND_MAGNETIC_MODULE_COORDS[0], + y: MOCK_SECOND_MAGNETIC_MODULE_COORDS[1], + z: MOCK_SECOND_MAGNETIC_MODULE_COORDS[2], + moduleDef: mockMagneticModule as any, + nestedLabwareDef: null, + nestedLabwareDisplayName: null, + nestedLabwareId: null, + slotName: 'B1', + protocolLoadOrder: 0, + attachedModuleMatch: null, + }, + ]) + when(mockBaseDeck) + .calledWith( + partialComponentPropsMatcher({ + robotType: FLEX_ROBOT_TYPE, + deckLayerBlocklist: getStandardDeckViewLayerBlockList( + FLEX_ROBOT_TYPE + ), + deckConfig: EXTENDED_DECK_CONFIG_FIXTURE, + labwareLocations: expect.anything(), + moduleLocations: expect.anything(), + }) + ) + .mockReturnValue(
mock BaseDeck
) + + const { getByText } = render({ + protocolAnalysis: protocolAnalysis, + }) + getByText('mock BaseDeck') }) }) diff --git a/app/src/molecules/DeckThumbnail/index.tsx b/app/src/molecules/DeckThumbnail/index.tsx index 314896186f2..fed45696e55 100644 --- a/app/src/molecules/DeckThumbnail/index.tsx +++ b/app/src/molecules/DeckThumbnail/index.tsx @@ -1,179 +1,128 @@ import * as React from 'react' -import { useSelector } from 'react-redux' import map from 'lodash/map' +import { BaseDeck } from '@opentrons/components' import { - RobotWorkSpace, - Module, - LabwareRender, - SlotLabels, - COLORS, -} from '@opentrons/components' -import { - inferModuleOrientationFromXCoordinate, - getModuleDef2, getDeckDefFromRobotType, getRobotTypeFromLoadedLabware, THERMOCYCLER_MODULE_V1, } from '@opentrons/shared-data' import { - parseInitialLoadedLabwareBySlot, - parseInitialLoadedLabwareByModuleId, - parseInitialLoadedModulesBySlot, - parseLiquidsInLoadOrder, - parseLabwareInfoByLiquidId, parseInitialLoadedLabwareByAdapter, + parseLabwareInfoByLiquidId, } from '@opentrons/api-client' -import { getWellFillFromLabwareId } from '../../organisms/Devices/ProtocolRun/SetupLiquids/utils' -import { getIsOnDevice } from '../../redux/config' + import { getStandardDeckViewLayerBlockList } from './utils/getStandardDeckViewLayerBlockList' -import { getStandardDeckViewBox } from './utils/getStandardViewBox' +import { getDeckConfigFromProtocolCommands } from '../../resources/deck_configuration/utils' +import { getLabwareRenderInfo } from '../../organisms/Devices/ProtocolRun/utils/getLabwareRenderInfo' +import { getProtocolModulesInfo } from '../../organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo' +import { useAttachedModules } from '../../organisms/Devices/hooks' +import { getAttachedProtocolModuleMatches } from '../../organisms/ProtocolSetupModulesAndDeck/utils' +import { getWellFillFromLabwareId } from '../../organisms/Devices/ProtocolRun/SetupLiquids/utils' import type { StyleProps } from '@opentrons/components' import type { - DeckSlot, - Liquid, - LoadedLabware, - RunTimeCommand, + CompletedProtocolAnalysis, + ProtocolAnalysisOutput, } from '@opentrons/shared-data' interface DeckThumbnailProps extends StyleProps { - commands: RunTimeCommand[] - labware: LoadedLabware[] - liquids?: Liquid[] + protocolAnalysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput | null showSlotLabels?: boolean } -export function DeckThumbnail(props: DeckThumbnailProps): JSX.Element { - const { - commands, - liquids, - labware = [], - showSlotLabels = false, - ...styleProps - } = props - const robotType = getRobotTypeFromLoadedLabware(labware) +export function DeckThumbnail(props: DeckThumbnailProps): JSX.Element | null { + const { protocolAnalysis, showSlotLabels = false, ...styleProps } = props + const attachedModules = useAttachedModules() + + if (protocolAnalysis == null) return null + + const robotType = getRobotTypeFromLoadedLabware(protocolAnalysis.labware) const deckDef = getDeckDefFromRobotType(robotType) - const initialLoadedLabwareBySlot = parseInitialLoadedLabwareBySlot(commands) const initialLoadedLabwareByAdapter = parseInitialLoadedLabwareByAdapter( - commands + protocolAnalysis.commands + ) + + const deckConfig = getDeckConfigFromProtocolCommands( + protocolAnalysis.commands ) - const initialLoadedModulesBySlot = parseInitialLoadedModulesBySlot(commands) - const initialLoadedLabwareByModuleId = parseInitialLoadedLabwareByModuleId( - commands + const liquids = protocolAnalysis.liquids + + const labwareRenderInfo = + protocolAnalysis != null + ? getLabwareRenderInfo(protocolAnalysis, deckDef) + : {} + const protocolModulesInfo = + protocolAnalysis != null + ? getProtocolModulesInfo(protocolAnalysis, deckDef) + : [] + const attachedProtocolModuleMatches = getAttachedProtocolModuleMatches( + attachedModules, + protocolModulesInfo ) - const liquidsInLoadOrder = parseLiquidsInLoadOrder( - liquids != null ? liquids : [], - commands + const labwareByLiquidId = parseLabwareInfoByLiquidId( + protocolAnalysis.commands ) - const labwareByLiquidId = parseLabwareInfoByLiquidId(commands) - const isOnDevice = useSelector(getIsOnDevice) - // TODO(bh, 2023-7-12): replace with color constant when added to design system - const deckFill = isOnDevice ? COLORS.light1 : '#e6e6e6' - return ( - // PR #10488 changed size - // revert the height - // Note add offset 18px to right and left - - {({ deckSlotsById }) => ( - <> - {map(deckSlotsById, (slot: DeckSlot, slotId: string) => { - if (slot.matingSurfaceUnitVector == null) return null // if slot has no mating surface, don't render anything in it + const moduleLocations = attachedProtocolModuleMatches.map(module => { + const labwareInAdapterInMod = + module.nestedLabwareId != null + ? initialLoadedLabwareByAdapter[module.nestedLabwareId] + : null + // only rendering the labware on top most layer so + // either the adapter or the labware are rendered but not both + const topLabwareDefinition = + labwareInAdapterInMod?.result?.definition ?? module.nestedLabwareDef + const nestedLabwareWellFill = getWellFillFromLabwareId( + module.nestedLabwareId ?? '', + liquids, + labwareByLiquidId + ) + // const labwareHasLiquid = !isEmpty(wellFill) + return { + moduleModel: module.moduleDef.model, + moduleLocation: { slotName: module.slotName }, + nestedLabwareWellFill, + innerProps: + module.moduleDef.model === THERMOCYCLER_MODULE_V1 + ? { lidMotorState: 'open' } + : {}, + nestedLabwareDef: topLabwareDefinition, + } + }) - const moduleInSlot = - slotId in initialLoadedModulesBySlot - ? initialLoadedModulesBySlot[slotId] - : null - const labwareInSlot = - slotId in initialLoadedLabwareBySlot - ? initialLoadedLabwareBySlot[slotId] - : null - const labwareInModule = - moduleInSlot?.result?.moduleId != null && - moduleInSlot.result.moduleId in initialLoadedLabwareByModuleId - ? initialLoadedLabwareByModuleId[moduleInSlot.result.moduleId] - : null + const labwareLocations = map( + labwareRenderInfo, + ({ labwareDef, displayName, slotName }, labwareId) => { + const labwareInAdapter = initialLoadedLabwareByAdapter[labwareId] + // only rendering the labware on top most layer so + // either the adapter or the labware are rendered but not both + const topLabwareDefinition = + labwareInAdapter?.result?.definition ?? labwareDef + const topLabwareId = labwareInAdapter?.result?.labwareId ?? labwareId - let labwareId = - labwareInSlot != null ? labwareInSlot.result?.labwareId : null - let labwareInAdapter = null + const wellFill = getWellFillFromLabwareId( + topLabwareId ?? '', + liquids, + labwareByLiquidId + ) + return { + labwareLocation: { slotName }, + definition: topLabwareDefinition, + wellFill: wellFill, + } + } + ) - if (labwareInModule != null) { - if ( - labwareInModule?.result != null && - 'labwareId' in labwareInModule.result && - labwareInModule.result.labwareId in - initialLoadedLabwareByAdapter - ) { - labwareInAdapter = - initialLoadedLabwareByAdapter[ - labwareInModule?.result.labwareId - ] - labwareId = labwareInAdapter.result?.labwareId - } else { - labwareId = labwareInModule.params.labwareId - } - } - const wellFill = - labwareId != null && liquids != null - ? getWellFillFromLabwareId( - labwareId, - liquidsInLoadOrder, - labwareByLiquidId - ) - : null - return ( - - {moduleInSlot != null ? ( - - {labwareInModule?.result?.definition != null ? ( - - ) : null} - - ) : null} - {labwareInSlot?.result?.definition != null ? ( - - - - ) : null} - - ) - })} - {showSlotLabels ? : null} - - )} - + return ( + ) } diff --git a/app/src/organisms/ChooseProtocolSlideout/index.tsx b/app/src/organisms/ChooseProtocolSlideout/index.tsx index 3e7b7b1fdfe..67a4148d54e 100644 --- a/app/src/organisms/ChooseProtocolSlideout/index.tsx +++ b/app/src/organisms/ChooseProtocolSlideout/index.tsx @@ -36,6 +36,7 @@ import { useCreateRunFromProtocol } from '../ChooseRobotToRunProtocolSlideout/us import { ApplyHistoricOffsets } from '../ApplyHistoricOffsets' import { useOffsetCandidatesForAnalysis } from '../ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' +import { ProtocolAnalysisOutput } from '@opentrons/shared-data' import type { Robot } from '../../redux/discovery/types' import type { StoredProtocolData } from '../../redux/protocol-storage' import type { State } from '../../redux/types' @@ -162,6 +163,7 @@ export function ChooseProtocolSlideoutComponent( }} robotName={robot.name} {...{ selectedProtocol, runCreationError, runCreationErrorCode }} + protocolAnalysis={selectedProtocol?.mostRecentAnalysis} /> ) : null} @@ -180,6 +182,7 @@ interface StoredProtocolListProps { runCreationError: string | null runCreationErrorCode: number | null robotName: string + protocolAnalysis?: ProtocolAnalysisOutput | null } function StoredProtocolList(props: StoredProtocolListProps): JSX.Element { @@ -189,6 +192,7 @@ function StoredProtocolList(props: StoredProtocolListProps): JSX.Element { runCreationError, runCreationErrorCode, robotName, + protocolAnalysis, } = props const { t } = useTranslation(['device_details', 'shared']) const storedProtocols = useSelector((state: State) => @@ -223,12 +227,9 @@ function StoredProtocolList(props: StoredProtocolListProps): JSX.Element { height="4.25rem" width="4.75rem" > - + {protocolAnalysis != null ? ( + + ) : null} - ) + const deckThumbnail = const deckViewByAnalysisStatus = { missing: , diff --git a/app/src/organisms/ProtocolsLanding/ProtocolCard.tsx b/app/src/organisms/ProtocolsLanding/ProtocolCard.tsx index 03325d54046..33c89cdd094 100644 --- a/app/src/organisms/ProtocolsLanding/ProtocolCard.tsx +++ b/app/src/organisms/ProtocolsLanding/ProtocolCard.tsx @@ -170,12 +170,12 @@ function AnalysisInfo(props: AnalysisInfoProps): JSX.Element { missing: , loading: , error: , - complete: ( - - ), + complete: + mostRecentAnalysis != null ? ( + + ) : ( + + ), }[analysisStatus] } diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDetails/Deck.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDetails/Deck.tsx index 286213fb915..ce1f640a6ce 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDetails/Deck.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDetails/Deck.tsx @@ -1,5 +1,7 @@ import * as React from 'react' import last from 'lodash/last' + +import { Flex } from '@opentrons/components' import { useProtocolAnalysisAsDocumentQuery, useProtocolQuery, @@ -7,8 +9,6 @@ import { import { DeckThumbnail } from '../../../molecules/DeckThumbnail' -import type { CompletedProtocolAnalysis } from '@opentrons/shared-data' - export const Deck = (props: { protocolId: string }): JSX.Element => { const { data: protocolData } = useProtocolQuery(props.protocolId) const { @@ -20,18 +20,10 @@ export const Deck = (props: { protocolId: string }): JSX.Element => { ) return ( - + + {mostRecentAnalysis != null ? ( + + ) : null} + ) } diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDetails/__tests__/Deck.test.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDetails/__tests__/Deck.test.tsx index 851d72ebec0..e634fe6ab68 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDetails/__tests__/Deck.test.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDetails/__tests__/Deck.test.tsx @@ -1,17 +1,154 @@ import * as React from 'react' -import { resetAllWhenMocks } from 'jest-when' +import { when, resetAllWhenMocks } from 'jest-when' + import { renderWithProviders } from '@opentrons/components' +import { + useProtocolAnalysisAsDocumentQuery, + useProtocolQuery, +} from '@opentrons/react-api-client' + import { i18n } from '../../../../i18n' import { DeckThumbnail } from '../../../../molecules/DeckThumbnail' import { Deck } from '../Deck' +import type { UseQueryResult } from 'react-query' +import type { CompletedProtocolAnalysis } from '@opentrons/shared-data' +import type { Protocol } from '@opentrons/api-client' + +jest.mock('@opentrons/react-api-client') jest.mock('../../../../molecules/DeckThumbnail') const mockDeckThumbnail = DeckThumbnail as jest.MockedFunction< typeof DeckThumbnail > +const mockUseProtocolAnalysisAsDocumentQuery = useProtocolAnalysisAsDocumentQuery as jest.MockedFunction< + typeof useProtocolAnalysisAsDocumentQuery +> +const mockUseProtocolQuery = useProtocolQuery as jest.MockedFunction< + typeof useProtocolQuery +> -const MOCK_PROTOCOL_ID = 'mock_protocol_id' +const MOCK_PROTOCOL_ID = 'mockProtocolId' +const MOCK_PROTOCOL_ANALYSIS = { + id: 'fake_protocol_analysis', + commands: [ + { + id: '97ba49a5-04f6-4f91-986a-04a0eb632882', + createdAt: '2022-09-07T19:47:42.781065+00:00', + commandType: 'loadPipette', + key: '0feeecaf-3895-46d7-ab71-564601265e35', + status: 'succeeded', + params: { + pipetteName: 'p20_single_gen2', + mount: 'left', + pipetteId: '90183a18-a1df-4fd6-9636-be3bcec63fe4', + }, + result: { + pipetteId: '90183a18-a1df-4fd6-9636-be3bcec63fe4', + }, + startedAt: '2022-09-07T19:47:42.782665+00:00', + completedAt: '2022-09-07T19:47:42.785061+00:00', + }, + { + id: '846e0b7b-1e54-4f42-9ab1-964ebda45da5', + createdAt: '2022-09-07T19:47:42.781281+00:00', + commandType: 'loadLiquid', + key: '1870d1a2-8dcd-46f2-9e27-16578365913b', + status: 'succeeded', + params: { + liquidId: '1', + labwareId: 'mockLabwareId1', + volumeByWell: { + A2: 20, + B2: 20, + C2: 20, + D2: 20, + E2: 20, + F2: 20, + G2: 20, + H2: 20, + }, + }, + result: {}, + startedAt: '2022-09-07T19:47:42.785987+00:00', + completedAt: '2022-09-07T19:47:42.786087+00:00', + }, + { + id: '1e03ae10-7e9b-465c-bc72-21ab5706bfb0', + createdAt: '2022-09-07T19:47:42.781323+00:00', + commandType: 'loadLiquid', + key: '48df9766-04ff-4927-9f2d-4efdcf0b3df8', + status: 'succeeded', + params: { + liquidId: '1', + labwareId: 'mockLabwareId2', + volumeByWell: { + D3: 40, + }, + }, + result: {}, + startedAt: '2022-09-07T19:47:42.786212+00:00', + completedAt: '2022-09-07T19:47:42.786285+00:00', + }, + { + id: '1e03ae10-7e9b-465c-bc72-21ab5706bfb0', + createdAt: '2022-09-07T19:47:42.781323+00:00', + commandType: 'loadLiquid', + key: '48df9766-04ff-4927-9f2d-4efdcf0b3df8', + status: 'succeeded', + params: { + liquidId: '1', + labwareId: 'mockLabwareId2', + volumeByWell: { + A3: 33, + B3: 33, + C3: 33, + }, + }, + result: {}, + startedAt: '2022-09-07T19:47:42.786212+00:00', + completedAt: '2022-09-07T19:47:42.786285+00:00', + }, + { + id: 'e8596bb3-b650-4d62-9bb5-dfc6e9e63249', + createdAt: '2022-09-07T19:47:42.781363+00:00', + commandType: 'loadLiquid', + key: '69d19b03-fdcc-4964-a2f8-3cbb30f4ddf3', + status: 'succeeded', + params: { + liquidId: '0', + labwareId: 'mockLabwareId1', + volumeByWell: { + A1: 33, + B1: 33, + C1: 33, + D1: 33, + E1: 33, + F1: 33, + G1: 33, + H1: 33, + }, + }, + result: {}, + startedAt: '2022-09-07T19:47:42.786347+00:00', + completedAt: '2022-09-07T19:47:42.786412+00:00', + }, + ], + liquids: [ + { + id: '1', + displayName: 'Saline', + description: 'mock liquid 2', + displayColor: '#b925ff', + }, + { + id: '0', + displayName: 'Water', + description: 'mock liquid 1', + displayColor: '#50d5ff', + }, + ], +} const render = (props: React.ComponentProps) => { return renderWithProviders(, { @@ -19,13 +156,27 @@ const render = (props: React.ComponentProps) => { }) } -describe('Hardware', () => { +describe('Deck', () => { let props: React.ComponentProps beforeEach(() => { props = { protocolId: MOCK_PROTOCOL_ID, } mockDeckThumbnail.mockReturnValue(
mock Deck Thumbnail
) + when(mockUseProtocolQuery) + .calledWith(MOCK_PROTOCOL_ID) + .mockReturnValue({ + data: { + data: { analysisSummaries: [{ id: MOCK_PROTOCOL_ANALYSIS.id }] }, + } as any, + } as UseQueryResult) + when(mockUseProtocolAnalysisAsDocumentQuery) + .calledWith(MOCK_PROTOCOL_ID, MOCK_PROTOCOL_ANALYSIS.id, { + enabled: true, + }) + .mockReturnValue({ + data: MOCK_PROTOCOL_ANALYSIS as any, + } as UseQueryResult) }) afterEach(() => { resetAllWhenMocks() diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx index 5d6138cd626..a30684a038b 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx @@ -19,6 +19,7 @@ import { truncateString, TYPOGRAPHY, POSITION_STICKY, + JUSTIFY_CENTER, } from '@opentrons/components' import { useCreateRunMutation, @@ -267,7 +268,14 @@ const ProtocolSectionContent = ({ protocolSection = break } - return {protocolSection} + return ( + + {protocolSection} + + ) } export function ProtocolDetails(): JSX.Element | null { diff --git a/components/src/hardware-sim/BaseDeck/BaseDeck.tsx b/components/src/hardware-sim/BaseDeck/BaseDeck.tsx index 9cd4ff9b4c2..1fe12ccdb91 100644 --- a/components/src/hardware-sim/BaseDeck/BaseDeck.tsx +++ b/components/src/hardware-sim/BaseDeck/BaseDeck.tsx @@ -1,4 +1,5 @@ import * as React from 'react' + import { RobotType, getDeckDefFromRobotType, @@ -35,12 +36,14 @@ import type { DeckConfiguration } from '@opentrons/shared-data' import type { TrashLocation } from '../Deck/FlexTrash' import type { StagingAreaLocation } from './StagingAreaFixture' import type { WasteChuteLocation } from './WasteChuteFixture' +import type { WellFill } from '../Labware' interface BaseDeckProps { robotType: RobotType labwareLocations: Array<{ labwareLocation: LabwareLocation definition: LabwareDefinition2 + wellFill?: WellFill // generic prop to render self-positioned children for each labware labwareChildren?: React.ReactNode onLabwareClick?: () => void @@ -49,6 +52,7 @@ interface BaseDeckProps { moduleModel: ModuleModel moduleLocation: ModuleLocation nestedLabwareDef?: LabwareDefinition2 | null + nestedLabwareWellFill?: WellFill innerProps?: React.ComponentProps['innerProps'] // generic prop to render self-positioned children for each module moduleChildren?: React.ReactNode @@ -60,6 +64,7 @@ interface BaseDeckProps { lightFill?: string darkFill?: string children?: React.ReactNode + showSlotLabels?: boolean } export function BaseDeck(props: BaseDeckProps): JSX.Element { @@ -75,6 +80,7 @@ export function BaseDeck(props: BaseDeckProps): JSX.Element { deckConfig = STANDARD_SLOT_DECK_CONFIG_FIXTURE, showExpansion = true, children, + showSlotLabels = false, } = props const deckDef = getDeckDefFromRobotType(robotType) @@ -153,6 +159,7 @@ export function BaseDeck(props: BaseDeckProps): JSX.Element { moduleModel, moduleLocation, nestedLabwareDef, + nestedLabwareWellFill, innerProps, moduleChildren, onLabwareClick, @@ -176,6 +183,7 @@ export function BaseDeck(props: BaseDeckProps): JSX.Element { ) : null} {moduleChildren} @@ -184,7 +192,13 @@ export function BaseDeck(props: BaseDeckProps): JSX.Element { } )} {labwareLocations.map( - ({ labwareLocation, definition, labwareChildren, onLabwareClick }) => { + ({ + labwareLocation, + definition, + labwareChildren, + wellFill, + onLabwareClick, + }) => { const slotDef = deckDef.locations.orderedSlots.find( s => labwareLocation !== 'offDeck' && @@ -195,17 +209,21 @@ export function BaseDeck(props: BaseDeckProps): JSX.Element { {labwareChildren} ) : null } )} - + {showSlotLabels ? ( + + ) : null} {children} )