Skip to content

Commit

Permalink
feat(app): usb connection and moam modal functionality in module setup (
Browse files Browse the repository at this point in the history
#8257)

* feat(app): usb connection and moam modal functionality in module setup
  • Loading branch information
jerader committed Sep 3, 2021
1 parent fe71591 commit da516da
Show file tree
Hide file tree
Showing 8 changed files with 463 additions and 93 deletions.
12 changes: 9 additions & 3 deletions app/src/assets/localization/en/protocol_setup.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"multiple_modules_help_link_title": "Multiple Modules Help",
"multiple_modules_modal_title": "Multiple Modules Help",
"labware_help_example": "<block>Alice is using a labware in Slot 6. During labware position check, she adjust the pipette position while checking the A1 of the labware to 0.2mm in X, and 1.2mm in Z. Later, Bob is preparing to run the same protocol on that robot. The labware offset that Alice created in Slot 6 will be applied for Bob’s protocol unless he changes or clears it.</block>",
"multiple_modules_explanation": "<block>To use multiples of a module in one protocol, you need to plug in the module that's in the lowest numbered deck slot in the lowest numbered USB port on the OT-2.</block> <block>Currently, you can use multiple Magnetic Modules or multiple Temperature Modules. You won't be able to load multiple Thermocycler Modules. <a_how_to_multiple_modules>Learn more about how to use multiples of a module </a_how_to_best_practices> </block>",
"multiple_modules_explanation": "<block>To use multiples of a module in one protocol, you need to plug in the module thats in the lowest numbered deck slot in the lowest numbered USB port on the OT-2.</block> <block>Currently, you can use multiple Magnetic Modules or multiple Temperature Modules. You wont be able to load multiple Thermocycler Modules. <a_how_to_multiple_modules>Learn more about how to use multiples of a module </a_how_to_best_practices> </block>",
"multiple_modules_example": "Your protocol has 2 Temperature Modules. The Temperature Module attached to the first port starting from the left will be related to the first Temperature Module in your protocol while the second Temperature Module loaded would be related to the Temperature Module connected to the next port to the right. If using a hub, follow the same logic with the port ordered.",
"offset_title": "Offset",
"labware_position_check_text": "Labware Position Check is an optional workflow that guides you through checking the position of each labware on the deck. During this check, you can make an offset adjustment to the overall position of the labware.",
Expand All @@ -25,8 +25,10 @@
"calibrate_now_cta": "Calibrate Now",
"deck_calibration_title": "Deck Calibration",
"last_calibrated": "Last calibrated: {{date}}",
"module_setup_step_description": "Plug in and power up the required module(s) via the OT-2 USB Port(s). Place the module(s) as shown in the deck map.",
"module_setup_step_description": "Plug in and power up the required module via the OT-2 USB Port. Place the module as shown in the deck map.",
"module_setup_step_description_plural": "Plug in and power up the required modules via the OT-2 USB Ports. Place the modules as shown in the deck map.",
"module_setup_step_title": "Module Setup",
"modules_setup_step_title": "Module Setup",
"mount_title": "{{mount}} MOUNT:",
"not_calibrated": "Not calibrated yet",
"proceed_to_labware_setup_step": "Proceed to Labware Setup",
Expand All @@ -39,5 +41,9 @@
"setup_for_run": "Setup for Run",
"step": "STEP {{index}}",
"module_not_connected": "Not Connected",
"no_usb_port_yet": "No USB Port Yet"
"module_connected": "Connected",
"no_usb_port_yet": "No USB Port Yet",
"usb_port_connected": "USB Port",
"hub_connected": "via hub",
"usb_port_connected_old_server_version": "USB Port Connected"
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import {
Text,
RobotCoordsForeignDiv,
Expand All @@ -7,16 +8,15 @@ import {
DIRECTION_ROW,
Flex,
Icon,
COLOR_ERROR,
FONT_STYLE_ITALIC,
FONT_BODY_1_DARK,
FONT_SIZE_BODY_2,
FONT_SIZE_CAPTION,
ALIGN_FLEX_START,
DISPLAY_FLEX,
JUSTIFY_FLEX_START,
COLOR_ERROR,
COLOR_SUCCESS,
} from '@opentrons/components'
import { useTranslation } from 'react-i18next'
import {
getModuleType,
ModuleModel,
Expand All @@ -30,13 +30,23 @@ export interface ModuleInfoProps {
y: number
orientation: 'left' | 'right'
moduleModel: ModuleModel
usbPort?: string | null
hubPort?: string | null
isAttached: boolean
}

export const ModuleInfo = (props: ModuleInfoProps): JSX.Element => {
const { x, y, orientation, moduleModel } = props
const { x, y, orientation, moduleModel, usbPort, hubPort, isAttached } = props
const moduleType = getModuleType(moduleModel)
const { t } = useTranslation('protocol_setup')
const { childYOffset } = getModuleVizDims(orientation, moduleType)
const moduleNotAttached = usbPort === null && hubPort === null && !isAttached
const moduleAttachedWithoutUSBNum =
usbPort === null && hubPort === null && isAttached
const moduleAttachedViaPort =
hubPort === null && usbPort !== null && isAttached
const moduleAttachedViaHub =
t('usb_port_connected') + ' ' + hubPort + ' ' + t('hub_connected')

return (
<RobotCoordsForeignDiv
Expand All @@ -54,24 +64,27 @@ export const ModuleInfo = (props: ModuleInfoProps): JSX.Element => {
<Flex flexDirection={DIRECTION_COLUMN}>
<Flex flexDirection={DIRECTION_ROW}>
<Icon
name="alert-circle"
name={isAttached ? 'check-circle' : 'alert-circle'}
color={isAttached ? COLOR_SUCCESS : COLOR_ERROR}
key="icon"
height="0.625rem"
width="0.625rem"
color={COLOR_ERROR}
marginRight={SPACING_1}
marginTop={SPACING_1}
/>
<Text css={FONT_SIZE_BODY_2} title={t('module_not_connected')}>
{t('module_not_connected')}
</Text>
<p>
{!isAttached ? t('module_not_connected') : t('module_connected')}
</p>
</Flex>
<Text css={FONT_BODY_1_DARK}>{getModuleDisplayName(moduleModel)}</Text>
<Text
fontSize={FONT_SIZE_CAPTION}
fontStyle={FONT_STYLE_ITALIC}
title={t('no_usb_port_yet')}
>
{t('no_usb_port_yet')}
<Text fontSize={FONT_SIZE_CAPTION} fontStyle={FONT_STYLE_ITALIC}>
{moduleNotAttached
? t('no_usb_port_yet')
: moduleAttachedWithoutUSBNum
? t('usb_port_connected_old_server_version')
: moduleAttachedViaPort
? t('usb_port_connected') + ' ' + usbPort
: moduleAttachedViaHub}
</Text>
</Flex>
</RobotCoordsForeignDiv>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from 'react'
import '@testing-library/jest-dom'
import { ModuleModel, ModuleRealType } from '@opentrons/shared-data'
import { ModuleInfo } from '../ModuleInfo'
import { renderWithProviders } from '@opentrons/components/__utils__'
import { i18n } from '../../../../../i18n'

const render = (props: React.ComponentProps<typeof ModuleInfo>) => {
return renderWithProviders(<ModuleInfo {...props} />, {
i18nInstance: i18n,
})
}
const STUBBED_ORIENTATION_VALUE = 'left'
const mockTCModule = {
labwareOffset: { x: 3, y: 3, z: 3 },
moduleId: 'TCModuleId',
model: 'thermocyclerModuleV1' as ModuleModel,
type: 'thermocyclerModuleType' as ModuleRealType,
}

describe('ModuleInfo', () => {
let props: React.ComponentProps<typeof ModuleInfo>
beforeEach(() => {
props = {
x: mockTCModule.labwareOffset.x,
y: mockTCModule.labwareOffset.y,
orientation: STUBBED_ORIENTATION_VALUE,
moduleModel: mockTCModule.model,
isAttached: false,
usbPort: null,
hubPort: null,
}
})

it('should show module not connected', () => {
const { getByText } = render(props)
expect(getByText('Not Connected')).toBeTruthy()
})

it('should show module connected and hub number', () => {
props = { ...props, usbPort: '1', hubPort: '1', isAttached: true }
const { getByText } = render(props)
expect(getByText('Connected')).toBeTruthy()
expect(getByText('USB Port 1 via hub')).toBeTruthy()
})

it('should show module connected and no USB number', () => {
props = { ...props, usbPort: null, hubPort: null, isAttached: true }
const { getByText } = render(props)
expect(getByText('Connected')).toBeTruthy()
expect(getByText('USB Port Connected')).toBeTruthy()
})

it('should show module connected and USB number', () => {
props = { ...props, usbPort: '1', hubPort: null, isAttached: true }
const { getByText } = render(props)
expect(getByText('Connected')).toBeTruthy()
expect(getByText('USB Port 1')).toBeTruthy()
})
})
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react'
import '@testing-library/jest-dom'
import { when, resetAllWhenMocks } from 'jest-when'
import { StaticRouter } from 'react-router-dom'
import { RobotWorkSpace, ModuleViz } from '@opentrons/components'
Expand All @@ -16,7 +17,13 @@ import {
ModuleModel,
ModuleRealType,
} from '@opentrons/shared-data'
import { getAttachedModules } from '../../../../../redux/modules'
import {
mockThermocycler as mockThermocyclerFixture,
mockMagneticModule as mockMagneticModuleFixture,
} from '../../../../../redux/modules/__fixtures__/index'

jest.mock('../../../../../redux/modules')
jest.mock('../ModuleInfo')
jest.mock('@opentrons/components', () => {
const actualComponents = jest.requireActual('@opentrons/components')
Expand All @@ -33,7 +40,9 @@ jest.mock('@opentrons/shared-data', () => {
inferModuleOrientationFromXCoordinate: jest.fn(),
}
})

const mockGetAttachedModules = getAttachedModules as jest.MockedFunction<
typeof getAttachedModules
>
const mockModuleInfo = ModuleInfo as jest.MockedFunction<typeof ModuleInfo>

const mockModuleViz = ModuleViz as jest.MockedFunction<typeof ModuleViz>
Expand Down Expand Up @@ -65,6 +74,7 @@ const render = (props: React.ComponentProps<typeof ModuleSetup>) => {
const STUBBED_ORIENTATION_VALUE = 'left'
const MOCK_MAGNETIC_MODULE_COORDS = [10, 20, 0]
const MOCK_TC_COORDS = [20, 30, 0]
const MOCK_ROBOT_NAME = 'ot-dev'

const mockMagneticModule = {
labwareOffset: { x: 5, y: 5, z: 5 },
Expand All @@ -83,7 +93,11 @@ const mockTCModule = {
describe('ModuleSetup', () => {
let props: React.ComponentProps<typeof ModuleSetup>
beforeEach(() => {
props = { moduleRenderCoords: {}, expandLabwareSetupStep: () => {} }
props = {
robotName: MOCK_ROBOT_NAME,
moduleRenderCoords: {},
expandLabwareSetupStep: () => {},
}

when(mockInferModuleOrientationFromXCoordinate)
.calledWith(expect.anything())
Expand All @@ -106,6 +120,9 @@ describe('ModuleSetup', () => {
})}
</div>
))
when(mockGetAttachedModules)
.calledWith(undefined as any, MOCK_ROBOT_NAME)
.mockReturnValue([])
})

afterEach(() => {
Expand All @@ -125,7 +142,86 @@ describe('ModuleSetup', () => {
expect(mockModuleViz).not.toHaveBeenCalled()
expect(mockModuleInfo).not.toHaveBeenCalled()
})
it('should render a deck WITH modules', () => {
it('should render a deck WITH modules with CTA disabled', () => {
const moduleRenderCoords = {
[mockMagneticModule.moduleId]: {
x: MOCK_MAGNETIC_MODULE_COORDS[0],
y: MOCK_MAGNETIC_MODULE_COORDS[1],
z: MOCK_MAGNETIC_MODULE_COORDS[2],
moduleModel: mockMagneticModule.model,
},
[mockTCModule.moduleId]: {
x: MOCK_TC_COORDS[0],
y: MOCK_TC_COORDS[1],
z: MOCK_TC_COORDS[2],
moduleModel: mockTCModule.model,
},
}

when(mockModuleViz)
.calledWith(
componentPropsMatcher({
orientation: STUBBED_ORIENTATION_VALUE,
moduleType: mockMagneticModule.type,
x: MOCK_MAGNETIC_MODULE_COORDS[0],
y: MOCK_MAGNETIC_MODULE_COORDS[1],
})
)
.mockReturnValue(<div>mock module viz {mockMagneticModule.type} </div>)

when(mockModuleViz)
.calledWith(
componentPropsMatcher({
orientation: STUBBED_ORIENTATION_VALUE,
moduleType: mockTCModule.type,
x: MOCK_TC_COORDS[0],
y: MOCK_TC_COORDS[1],
})
)
.mockReturnValue(<div>mock module viz {mockTCModule.type} </div>)

when(mockModuleInfo)
.calledWith(
componentPropsMatcher({
orientation: STUBBED_ORIENTATION_VALUE,
moduleModel: mockMagneticModule.model,
x: MOCK_MAGNETIC_MODULE_COORDS[0],
y: MOCK_MAGNETIC_MODULE_COORDS[1],
isAttached: false,
usbPort: null,
hubPort: null,
})
)
.mockReturnValue(<div>mock module info {mockMagneticModule.model} </div>)

when(mockModuleInfo)
.calledWith(
componentPropsMatcher({
orientation: STUBBED_ORIENTATION_VALUE,
moduleModel: mockTCModule.model,
x: MOCK_TC_COORDS[0],
y: MOCK_TC_COORDS[1],
isAttached: false,
usbPort: null,
hubPort: null,
})
)
.mockReturnValue(<div>mock module info {mockTCModule.model} </div>)

props = {
...props,
moduleRenderCoords,
}

const { getByText, getByRole } = render(props)
getByText('mock module viz magneticModuleType')
getByText('mock module viz thermocyclerModuleType')
getByText('mock module info magneticModuleV2')
const button = getByRole('button', { name: 'Proceed to Labware Setup' })
expect(button).toHaveAttribute('disabled')
})

it('should render a deck WITH modules with CTA enabled', () => {
const moduleRenderCoords = {
[mockMagneticModule.moduleId]: {
x: MOCK_MAGNETIC_MODULE_COORDS[0],
Expand All @@ -140,6 +236,15 @@ describe('ModuleSetup', () => {
moduleModel: mockTCModule.model,
},
}
when(mockGetAttachedModules)
.calledWith(undefined as any, MOCK_ROBOT_NAME)
.mockReturnValue([
{
...mockMagneticModuleFixture,
model: mockMagneticModule.model,
} as any,
{ ...mockThermocyclerFixture, model: mockTCModule.model } as any,
])

when(mockModuleViz)
.calledWith(
Expand Down Expand Up @@ -170,6 +275,9 @@ describe('ModuleSetup', () => {
moduleModel: mockMagneticModule.model,
x: MOCK_MAGNETIC_MODULE_COORDS[0],
y: MOCK_MAGNETIC_MODULE_COORDS[1],
isAttached: true,
usbPort: String(mockMagneticModuleFixture.usbPort.port),
hubPort: String(mockMagneticModuleFixture.usbPort.hub),
})
)
.mockReturnValue(<div>mock module info {mockMagneticModule.model} </div>)
Expand All @@ -181,6 +289,9 @@ describe('ModuleSetup', () => {
moduleModel: mockTCModule.model,
x: MOCK_TC_COORDS[0],
y: MOCK_TC_COORDS[1],
isAttached: true,
usbPort: String(mockThermocyclerFixture.usbPort.port),
hubPort: String(mockThermocyclerFixture.usbPort.hub),
})
)
.mockReturnValue(<div>mock module info {mockTCModule.model} </div>)
Expand All @@ -190,9 +301,11 @@ describe('ModuleSetup', () => {
moduleRenderCoords,
}

const { getByText } = render(props)
const { getByText, getByRole } = render(props)
getByText('mock module viz magneticModuleType')
getByText('mock module viz thermocyclerModuleType')
getByText('mock module info magneticModuleV2')
const button = getByRole('button', { name: 'Proceed to Labware Setup' })
expect(button).not.toHaveAttribute('disabled')
})
})
Loading

0 comments on commit da516da

Please sign in to comment.