Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(app): usb connection and moam modal functionality in module setup #8257

Merged
merged 39 commits into from
Sep 3, 2021
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
c6b2b14
create module setup component and test
jerader Aug 6, 2021
eaf9892
create multiple modules modal and edit styles
jerader Aug 9, 2021
fd5e406
create module info and edited MoaM modal
jerader Aug 10, 2021
1e2f00d
address pr comments
jerader Aug 12, 2021
dc80ed7
fix test and deck view box
jerader Aug 12, 2021
090ce8d
fix styling and svg tag
jerader Aug 16, 2021
6441bdb
fix margin error
jerader Aug 16, 2021
296ba27
add poll the robot modules ever 5 seconds
jerader Aug 16, 2021
25c156a
add some usb info
jerader Aug 16, 2021
472558b
fix connected and not connected
jerader Aug 16, 2021
b9b783d
add button styling
jerader Aug 17, 2021
88e4e9b
begin to add usb detection
jerader Aug 18, 2021
aa0a4ff
add frontend port and module connection
jerader Aug 18, 2021
8875e36
modify css file
jerader Aug 19, 2021
522e112
fix module variable
jerader Aug 19, 2021
c5e984a
remove styles file and edit usb and hub
jerader Aug 19, 2021
866799b
add port and mode features
jerader Aug 23, 2021
1d9bfcb
style fix on labware setup file
jerader Aug 23, 2021
fb91ff7
fix hub and port connection
jerader Aug 23, 2021
5daa14c
add isAttached and fix usb and hub
jerader Aug 23, 2021
b8496f5
fix for both port and no port info compatibility
jerader Aug 24, 2021
9e35078
add labware setup step btn function
jerader Aug 24, 2021
2942f2f
add moam modal functionality
jerader Aug 24, 2021
377d8b3
reorg
jerader Aug 24, 2021
e04e11d
modify tests and fix labware setup button fxn
jerader Aug 25, 2021
eec117d
modify tests
jerader Aug 25, 2021
d0b08a8
feat(app): usb connection and moam modal functionality in module setup
jerader Aug 25, 2021
d21e5cc
clean up code
jerader Aug 26, 2021
111e4af
fix prettier formatting
jerader Aug 26, 2021
9263ca3
add test and fix robotname functionality
jerader Aug 26, 2021
a26d83c
remove unnecessary imports
jerader Aug 26, 2021
a02cd01
address comments and rework moduleInfo test
jerader Aug 27, 2021
133bde6
add expect corresponding matcher call to moduleInfo test
jerader Aug 27, 2021
2c3581f
fix import
jerader Aug 30, 2021
a52334c
address Emilys comment
jerader Aug 30, 2021
dc64a67
correct prettier formatting
jerader Aug 30, 2021
52696fe
address comments
jerader Sep 1, 2021
7109e2f
utilize i18n plural capabilities
jerader Sep 3, 2021
e8334fb
fix prettier formatting
jerader Sep 3, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.",
"modules_setup_step_description": "Plug in and power up the required modules via the OT-2 USB Ports. Place the modules as shown in the deck map.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need both of these?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the extra is needed to pluralize, i18n can handle that for us i think: https://www.i18next.com/translation-function/plurals

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something that Emily wanted to implement. She mentioned it in a comment on my last PR #8224

"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": "USB Port Connected"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usb_port_connected_old makes this seem like we don't need it anymore. what is its purpose?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If your robot server version is below 4.3, the usb and hub ports don't have a string (I double checked with my robot when it was version 4.0). So usb_port_connected_old is to support that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe making this title more descriptive, something like usb_port_connected_old_server_version could make this more clear

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ 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 {
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 @@ -55,23 +65,26 @@ export const ModuleInfo = (props: ModuleInfoProps): JSX.Element => {
<Flex flexDirection={DIRECTION_ROW}>
<Icon
name="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')
: 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