Skip to content

Commit

Permalink
refactor(app): load modules to retrieve moduleId for run setup module…
Browse files Browse the repository at this point in the history
… controls (#10451)

* refactor(app): load modules to retrieve moduleId for run setup module controls

This PR loads the modules in the protocol and then retrieves the moduleId from the protocol analysis
and feeds it to the corresponding createCommand for each module action.
  • Loading branch information
sakibh authored May 27, 2022
1 parent 1e97a69 commit 3dffaef
Show file tree
Hide file tree
Showing 27 changed files with 493 additions and 56 deletions.
35 changes: 28 additions & 7 deletions app/src/organisms/Devices/HeaterShakerWizard/TestShake.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import React from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { useCreateLiveCommandMutation } from '@opentrons/react-api-client'
import {
useCreateCommandMutation,
useCreateLiveCommandMutation,
} from '@opentrons/react-api-client'
import {
ALIGN_CENTER,
ALIGN_FLEX_START,
Expand All @@ -23,6 +26,7 @@ import { InputField } from '../../../atoms/InputField'
import { Collapsible } from '../ModuleCard/Collapsible'
import { useLatchControls } from '../ModuleCard/hooks'
import { HeaterShakerModuleCard } from './HeaterShakerModuleCard'
import { useModuleIdFromRun } from '../ModuleCard/useModuleIdFromRun'

import type { HeaterShakerModule } from '../../../redux/modules/types'
import type {
Expand All @@ -35,35 +39,52 @@ interface TestShakeProps {
module: HeaterShakerModule
setCurrentPage: React.Dispatch<React.SetStateAction<number>>
moduleFromProtocol?: ProtocolModuleInfo
runId?: string
}

export function TestShake(props: TestShakeProps): JSX.Element {
const { module, setCurrentPage, moduleFromProtocol } = props
const { module, setCurrentPage, moduleFromProtocol, runId } = props
const { t } = useTranslation(['heater_shaker', 'device_details'])
const { createLiveCommand } = useCreateLiveCommandMutation()
const { createCommand } = useCreateCommandMutation()
const [isExpanded, setExpanded] = React.useState(false)
const [shakeValue, setShakeValue] = React.useState<string | null>(null)
const [targetProps, tooltipProps] = useHoverTooltip()
const { toggleLatch, isLatchClosed } = useLatchControls(module)
const { toggleLatch, isLatchClosed } = useLatchControls(module, runId)
const { moduleIdFromRun } = useModuleIdFromRun(
module,
runId != null ? runId : null
)
const isShaking = module.data.speedStatus !== 'idle'

const setShakeCommand: HeaterShakerSetTargetShakeSpeedCreateCommand = {
commandType: 'heaterShakerModule/setTargetShakeSpeed',
params: {
moduleId: module.id,
moduleId: runId != null ? moduleIdFromRun : module.id,
rpm: shakeValue !== null ? parseInt(shakeValue) : 0,
},
}

const stopShakeCommand: HeaterShakerStopShakeCreateCommand = {
commandType: 'heaterShakerModule/stopShake',
params: {
moduleId: module.id,
moduleId: runId != null ? moduleIdFromRun : module.id,
},
}

const handleShakeCommand = (): void => {
if (shakeValue !== null) {
if (runId != null) {
createCommand({
runId: runId,
command: isShaking ? stopShakeCommand : setShakeCommand,
}).catch((e: Error) => {
console.error(
`error setting module status with command type ${
stopShakeCommand.commandType ?? setShakeCommand.commandType
} and run id ${runId}: ${e.message}`
)
})
} else {
createLiveCommand({
command: isShaking ? stopShakeCommand : setShakeCommand,
}).catch((e: Error) => {
Expand Down Expand Up @@ -184,7 +205,7 @@ export function TestShake(props: TestShakeProps): JSX.Element {
marginLeft={SIZE_AUTO}
marginTop={SPACING.spacing4}
onClick={handleShakeCommand}
disabled={!isLatchClosed}
disabled={!isLatchClosed || (shakeValue === null && !isShaking)}
{...targetProps}
>
{isShaking ? t('stop_shaking') : t('start_shaking')}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
import * as React from 'react'
import { nestedTextMatcher, renderWithProviders } from '@opentrons/components'
import { fireEvent } from '@testing-library/react'
import { useCreateLiveCommandMutation } from '@opentrons/react-api-client'
import {
useCreateCommandMutation,
useCreateLiveCommandMutation,
} from '@opentrons/react-api-client'
import { i18n } from '../../../../i18n'
import { useLatchControls } from '../../ModuleCard/hooks'
import { TestShake } from '../TestShake'
import { HeaterShakerModuleCard } from '../HeaterShakerModuleCard'
import heaterShakerCommands from '@opentrons/shared-data/protocol/fixtures/6/heaterShakerCommands.json'
import { mockHeaterShaker } from '../../../../redux/modules/__fixtures__'
import { useModuleIdFromRun } from '../../ModuleCard/useModuleIdFromRun'

import type { ProtocolModuleInfo } from '../../../Devices/ProtocolRun/utils/getProtocolModulesInfo'

jest.mock('@opentrons/react-api-client')
jest.mock('../HeaterShakerModuleCard')
jest.mock('../../ModuleCard/hooks')
jest.mock('../../ModuleCard/useModuleIdFromRun')

const mockUseLiveCommandMutation = useCreateLiveCommandMutation as jest.MockedFunction<
typeof useCreateLiveCommandMutation
>
const mockUseCommandMutation = useCreateCommandMutation as jest.MockedFunction<
typeof useCreateCommandMutation
>
const mockUseLatchControls = useLatchControls as jest.MockedFunction<
typeof useLatchControls
>
const mockHeaterShakerModuleCard = HeaterShakerModuleCard as jest.MockedFunction<
typeof HeaterShakerModuleCard
>
const mockUseModuleIdFromRun = useModuleIdFromRun as jest.MockedFunction<
typeof useModuleIdFromRun
>

const render = (props: React.ComponentProps<typeof TestShake>) => {
return renderWithProviders(<TestShake {...props} />, {
Expand Down Expand Up @@ -113,6 +124,7 @@ const mockMovingHeaterShaker = {
describe('TestShake', () => {
let props: React.ComponentProps<typeof TestShake>
let mockCreateLiveCommand = jest.fn()
let mockCreateCommand = jest.fn()
beforeEach(() => {
props = {
setCurrentPage: jest.fn(),
Expand All @@ -124,13 +136,20 @@ describe('TestShake', () => {
mockUseLiveCommandMutation.mockReturnValue({
createLiveCommand: mockCreateLiveCommand,
} as any)
mockCreateCommand = jest.fn()
mockUseCommandMutation.mockReturnValue({
createCommand: mockCreateCommand,
} as any)
mockHeaterShakerModuleCard.mockReturnValue(
<div>Mock Heater Shaker Module Card</div>
)
mockUseLatchControls.mockReturnValue({
handleLatch: jest.fn(),
isLatchClosed: true,
} as any)
mockUseModuleIdFromRun.mockReturnValue({
moduleIdFromRun: 'heatershaker_id',
})
})
it('renders the correct title', () => {
const { getByText } = render(props)
Expand Down Expand Up @@ -191,7 +210,7 @@ describe('TestShake', () => {

const { getByRole } = render(props)
const button = getByRole('button', { name: /Start Shaking/i })
expect(button).toBeEnabled()
expect(button).toBeDisabled()
})

it('renders an input field for speed setting', () => {
Expand Down
4 changes: 3 additions & 1 deletion app/src/organisms/Devices/HeaterShakerWizard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ import type { ProtocolModuleInfo } from '../../Devices/ProtocolRun/utils/getProt
interface HeaterShakerWizardProps {
onCloseClick: () => unknown
moduleFromProtocol?: ProtocolModuleInfo
runId?: string
}

export const HeaterShakerWizard = (
props: HeaterShakerWizardProps
): JSX.Element | null => {
const { onCloseClick, moduleFromProtocol } = props
const { onCloseClick, moduleFromProtocol, runId } = props
const { t } = useTranslation(['heater_shaker', 'shared'])
const [currentPage, setCurrentPage] = React.useState(0)
const { robotName } = useParams<NavRouteParams>()
Expand Down Expand Up @@ -99,6 +100,7 @@ export const HeaterShakerWizard = (
module={heaterShaker}
setCurrentPage={setCurrentPage}
moduleFromProtocol={moduleFromProtocol}
runId={runId}
/>
) : null
)
Expand Down
9 changes: 7 additions & 2 deletions app/src/organisms/Devices/ModuleCard/HeaterShakerSlideout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
TYPOGRAPHY,
useConditionalConfirm,
} from '@opentrons/components'
import { useModuleIdFromRun } from './useModuleIdFromRun'
import { PrimaryButton } from '../../../atoms/buttons'
import { getIsHeaterShakerAttached } from '../../../redux/config'
import { InputField } from '../../../atoms/InputField'
Expand Down Expand Up @@ -54,14 +55,18 @@ export const HeaterShakerSlideout = (
const { createCommand } = useCreateCommandMutation()
const moduleName = getModuleDisplayName(module.moduleModel)
const configHasHeaterShakerAttached = useSelector(getIsHeaterShakerAttached)
const { moduleIdFromRun } = useModuleIdFromRun(
module,
runId != null ? runId : null
)
const modulePart = isSetShake ? t('shake_speed') : t('temperature')

const sendShakeSpeedCommand = (): void => {
if (hsValue != null && isSetShake) {
const setShakeCommand: HeaterShakerSetTargetShakeSpeedCreateCommand = {
commandType: 'heaterShakerModule/setTargetShakeSpeed',
params: {
moduleId: module.id,
moduleId: runId != null ? moduleIdFromRun : module.id,
rpm: parseInt(hsValue),
},
}
Expand Down Expand Up @@ -98,7 +103,7 @@ export const HeaterShakerSlideout = (
const setTempCommand: HeaterShakerStartSetTargetTemperatureCreateCommand = {
commandType: 'heaterShakerModule/startSetTargetTemperature',
params: {
moduleId: module.id,
moduleId: runId != null ? moduleIdFromRun : module.id,
// @ts-expect-error TODO: remove this after https://github.com/Opentrons/opentrons/pull/10182 merges
temperature: parseInt(hsValue),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
useCreateCommandMutation,
useCreateLiveCommandMutation,
} from '@opentrons/react-api-client'
import { useModuleIdFromRun } from './useModuleIdFromRun'
import {
Flex,
Text,
Expand Down Expand Up @@ -80,6 +81,10 @@ export const MagneticModuleSlideout = (
const [engageHeightValue, setEngageHeightValue] = React.useState<
string | null
>(null)
const { moduleIdFromRun } = useModuleIdFromRun(
module,
runId != null ? runId : null
)

const moduleName = getModuleDisplayName(module.moduleModel)
const info = getInfoByModel(module.moduleModel)
Expand Down Expand Up @@ -114,7 +119,7 @@ export const MagneticModuleSlideout = (
const setEngageCommand: MagneticModuleEngageMagnetCreateCommand = {
commandType: 'magneticModule/engage',
params: {
moduleId: module.id,
moduleId: runId != null ? moduleIdFromRun : module.id,
height: parseInt(engageHeightValue),
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const ModuleOverflowMenu = (
handleTestShakeClick,
handleWizardClick,
} = props

const [targetProps, tooltipProps] = useHoverTooltip()
const { menuOverflowItemsByModuleType } = useModuleOverflowMenu(
module,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const TemperatureModuleData = (
data-testid={`temp_module_data`}
>
<Text marginBottom={SPACING.spacing1}>
{t(targetTemp === null ? 'na_temp' : 'target_temp', {
{t(targetTemp == null ? 'na_temp' : 'target_temp', {
temp: targetTemp,
})}
</Text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
TEMP_MAX,
TEMP_MIN,
} from '@opentrons/shared-data'
import { useModuleIdFromRun } from './useModuleIdFromRun'
import { Slideout } from '../../../atoms/Slideout'
import { PrimaryButton } from '../../../atoms/buttons'
import { InputField } from '../../../atoms/InputField'
Expand All @@ -40,6 +41,10 @@ export const TemperatureModuleSlideout = (
const { t } = useTranslation('device_details')
const { createLiveCommand } = useCreateLiveCommandMutation()
const { createCommand } = useCreateCommandMutation()
const { moduleIdFromRun } = useModuleIdFromRun(
module,
runId != null ? runId : null
)
const name = getModuleDisplayName(module.moduleModel)
const [temperatureValue, setTemperatureValue] = React.useState<string | null>(
null
Expand All @@ -50,7 +55,7 @@ export const TemperatureModuleSlideout = (
const saveTempCommand: TemperatureModuleSetTargetTemperatureCreateCommand = {
commandType: 'temperatureModule/setTargetTemperature',
params: {
moduleId: module.id,
moduleId: runId != null ? moduleIdFromRun : module.id,
celsius: parseInt(temperatureValue),
},
}
Expand Down
55 changes: 29 additions & 26 deletions app/src/organisms/Devices/ModuleCard/TestShakeSlideout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { InputField } from '../../../atoms/InputField'
import { Tooltip } from '../../../atoms/Tooltip'
import { HeaterShakerWizard } from '../HeaterShakerWizard'
import { useLatchControls } from './hooks'
import { useModuleIdFromRun } from './useModuleIdFromRun'
import { Collapsible } from './Collapsible'

import type { HeaterShakerModule } from '../../../redux/modules/types'
Expand All @@ -60,6 +61,10 @@ export const TestShakeSlideout = (
const name = getModuleDisplayName(module.moduleModel)
const [targetProps, tooltipProps] = useHoverTooltip()
const { toggleLatch, isLatchClosed } = useLatchControls(module, runId)
const { moduleIdFromRun } = useModuleIdFromRun(
module,
runId != null ? runId : null
)

const [showCollapsed, setShowCollapsed] = React.useState(false)
const [shakeValue, setShakeValue] = React.useState<string | null>(null)
Expand All @@ -69,42 +74,40 @@ export const TestShakeSlideout = (
const setShakeCommand: HeaterShakerSetTargetShakeSpeedCreateCommand = {
commandType: 'heaterShakerModule/setTargetShakeSpeed',
params: {
moduleId: module.id,
moduleId: runId != null ? moduleIdFromRun : module.id,
rpm: shakeValue !== null ? parseInt(shakeValue) : 0,
},
}

const stopShakeCommand: HeaterShakerStopShakeCreateCommand = {
commandType: 'heaterShakerModule/stopShake',
params: {
moduleId: module.id,
moduleId: runId != null ? moduleIdFromRun : module.id,
},
}

const handleShakeCommand = (): void => {
if (shakeValue !== null) {
if (runId != null) {
createCommand({
runId: runId,
command: isShaking ? stopShakeCommand : setShakeCommand,
}).catch((e: Error) => {
console.error(
`error setting module status with command type ${
stopShakeCommand.commandType ?? setShakeCommand.commandType
}: ${e.message}`
)
})
} else {
createLiveCommand({
command: isShaking ? stopShakeCommand : setShakeCommand,
}).catch((e: Error) => {
console.error(
`error setting module status with command type ${
stopShakeCommand.commandType ?? setShakeCommand.commandType
}: ${e.message}`
)
})
}
if (runId != null) {
createCommand({
runId: runId,
command: isShaking ? stopShakeCommand : setShakeCommand,
}).catch((e: Error) => {
console.error(
`error setting module status with command type ${
stopShakeCommand.commandType ?? setShakeCommand.commandType
}: ${e.message}`
)
})
} else {
createLiveCommand({
command: isShaking ? stopShakeCommand : setShakeCommand,
}).catch((e: Error) => {
console.error(
`error setting module status with command type ${
stopShakeCommand.commandType ?? setShakeCommand.commandType
}: ${e.message}`
)
})
}
setShakeValue(null)
}
Expand Down Expand Up @@ -249,7 +252,7 @@ export const TestShakeSlideout = (
marginTop={SPACING.spacing3}
paddingX={SPACING.spacing4}
onClick={handleShakeCommand}
disabled={!isLatchClosed}
disabled={!isLatchClosed || (shakeValue === null && !isShaking)}
{...targetProps}
>
{isShaking
Expand Down
Loading

0 comments on commit 3dffaef

Please sign in to comment.