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

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

Merged
merged 11 commits into from
May 27, 2022
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
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