From d54f47c481ec116c942a188612b94e793143f675 Mon Sep 17 00:00:00 2001 From: Sakib Hossain Date: Fri, 25 Feb 2022 14:19:21 -0500 Subject: [PATCH 1/5] refactor(app): add heater shaker types This PR adds the constants and types necessary for the heater shaker module in shared-data and redux. --- .../hardware_control/modules/heater_shaker.py | 2 +- app/src/organisms/Devices/ModuleIcon.tsx | 3 ++ app/src/redux/modules/api-types.ts | 35 +++++++++++++++++++ app/src/redux/modules/constants.ts | 1 + .../redux/modules/epic/fetchModulesEpic.ts | 18 ++++++++++ app/src/redux/modules/types.ts | 10 ++++++ components/src/icons/icon-data.ts | 2 +- .../service/legacy/models/modules.py | 2 +- .../integration/test_modules.tavern.yaml | 2 +- .../service/legacy/routers/test_modules.py | 2 +- shared-data/js/constants.ts | 8 +++++ shared-data/js/modules.ts | 6 ++++ shared-data/js/types.ts | 6 ++++ 13 files changed, 92 insertions(+), 5 deletions(-) diff --git a/api/src/opentrons/hardware_control/modules/heater_shaker.py b/api/src/opentrons/hardware_control/modules/heater_shaker.py index 6bcea72e4b5..b557e57fb7c 100644 --- a/api/src/opentrons/hardware_control/modules/heater_shaker.py +++ b/api/src/opentrons/hardware_control/modules/heater_shaker.py @@ -110,7 +110,7 @@ def name(cls) -> str: @staticmethod def _model_from_revision(revision: Optional[str]) -> str: """Defines the revision -> model mapping""" - return "heaterShakerV1" + return "heaterShakerModuleV1" @staticmethod def _get_temperature_status(temperature: Temperature) -> TemperatureStatus: diff --git a/app/src/organisms/Devices/ModuleIcon.tsx b/app/src/organisms/Devices/ModuleIcon.tsx index 638d92ac640..347930569db 100644 --- a/app/src/organisms/Devices/ModuleIcon.tsx +++ b/app/src/organisms/Devices/ModuleIcon.tsx @@ -1,5 +1,6 @@ import * as React from 'react' import { + HEATERSHAKER_MODULE_TYPE, MAGNETIC_MODULE_TYPE, TEMPERATURE_MODULE_TYPE, THERMOCYCLER_MODULE_TYPE, @@ -11,6 +12,7 @@ interface ModuleIconProps { | typeof MAGNETIC_MODULE_TYPE | typeof TEMPERATURE_MODULE_TYPE | typeof THERMOCYCLER_MODULE_TYPE + | typeof HEATERSHAKER_MODULE_TYPE } export const ModuleIcon = (props: ModuleIconProps): JSX.Element => { @@ -19,6 +21,7 @@ export const ModuleIcon = (props: ModuleIconProps): JSX.Element => { [MAGNETIC_MODULE_TYPE]: 'ot-magnet-v2', [TEMPERATURE_MODULE_TYPE]: 'ot-temperature-v2', [THERMOCYCLER_MODULE_TYPE]: 'ot-thermocycler', + [HEATERSHAKER_MODULE_TYPE]: 'ot-heater-shaker', } as const return ( diff --git a/app/src/redux/modules/api-types.ts b/app/src/redux/modules/api-types.ts index f73df92745b..9dab9dfa855 100644 --- a/app/src/redux/modules/api-types.ts +++ b/app/src/redux/modules/api-types.ts @@ -4,6 +4,7 @@ import type { MagneticModuleModel, TemperatureModuleModel, ThermocyclerModuleModel, + HeaterShakerModuleModel, ModuleModel, } from '@opentrons/shared-data' @@ -58,6 +59,16 @@ export interface ThermocyclerData { totalCycleCount: number | null currentCycleIndex: number | null } +export interface HeaterShakerData { + labwareLatchStatus: LatchStatus + speedStatus: SpeedStatus + temperatureStatus: TemperatureStatus + currentSpeed: number | null + currentTemp: number | null + targetSpeed: number | null + targetTemp: number | null + errorDetails: string | null +} export type TemperatureStatus = | 'idle' @@ -74,6 +85,23 @@ export type ThermocyclerStatus = export type MagneticStatus = 'engaged' | 'disengaged' +export type HeaterShakerStatus = 'idle' | 'running' | 'error' + +export type SpeedStatus = + | 'holding at target' + | 'speeding up' + | 'slowing down' + | 'idle' + | 'error' + +export type LatchStatus = + | 'opening' + | 'idle_open' + | 'closing' + | 'idle_closed' + | 'idle_unknown' + | 'unknown' + export interface ApiTemperatureModule extends ApiBaseModule { moduleModel: TemperatureModuleModel name: typeof TEMPDECK @@ -113,10 +141,17 @@ export interface ApiThermocyclerModuleLegacy extends ApiBaseModuleLegacy { status: ThermocyclerStatus } +export interface ApiHeaterShakerModule extends ApiBaseModule { + moduleModel: HeaterShakerModuleModel + data: HeaterShakerData + status: HeaterShakerStatus +} + export type ApiAttachedModule = | ApiThermocyclerModule | ApiMagneticModule | ApiTemperatureModule + | ApiHeaterShakerModule export type ApiAttachedModuleLegacy = | ApiThermocyclerModuleLegacy diff --git a/app/src/redux/modules/constants.ts b/app/src/redux/modules/constants.ts index 2b3803a9c37..5ccca8e830b 100644 --- a/app/src/redux/modules/constants.ts +++ b/app/src/redux/modules/constants.ts @@ -8,6 +8,7 @@ export { MAGNETIC_MODULE_TYPE, TEMPERATURE_MODULE_TYPE, THERMOCYCLER_MODULE_TYPE, + HEATERSHAKER_MODULE_TYPE, } from '@opentrons/shared-data' // http paths diff --git a/app/src/redux/modules/epic/fetchModulesEpic.ts b/app/src/redux/modules/epic/fetchModulesEpic.ts index 8e3d98ec233..d57b55ef166 100644 --- a/app/src/redux/modules/epic/fetchModulesEpic.ts +++ b/app/src/redux/modules/epic/fetchModulesEpic.ts @@ -9,17 +9,20 @@ import { MAGNETIC_MODULE_TYPE, TEMPERATURE_MODULE_TYPE, THERMOCYCLER_MODULE_TYPE, + HEATERSHAKER_MODULE_TYPE, TEMPERATURE_MODULE_V1, TEMPERATURE_MODULE_V2, MAGNETIC_MODULE_V1, MAGNETIC_MODULE_V2, THERMOCYCLER_MODULE_V1, + HEATERSHAKER_MODULE_V1, } from '@opentrons/shared-data' import type { MagneticModuleModel, TemperatureModuleModel, ThermocyclerModuleModel, + HeaterShakerModuleModel, } from '@opentrons/shared-data' import * as Actions from '../actions' @@ -42,6 +45,8 @@ import type { TemperatureStatus, MagneticStatus, ThermocyclerStatus, + HeaterShakerData, + HeaterShakerStatus, } from '../api-types' const mapActionToRequest: ActionToRequestMapper = action => ({ @@ -68,6 +73,12 @@ type IdentifierWithData = data: ThermocyclerData status: ThermocyclerStatus } + | { + type: typeof HEATERSHAKER_MODULE_TYPE + model: HeaterShakerModuleModel + data: HeaterShakerData + status: HeaterShakerStatus + } const normalizeModuleInfoLegacy = ( response: ApiAttachedModuleLegacy @@ -126,6 +137,13 @@ const normalizeModuleInfoNew = ( data: response.data, status: response.status, } + case HEATERSHAKER_MODULE_V1: + return { + model: response.moduleModel, + type: HEATERSHAKER_MODULE_TYPE, + data: response.data, + status: response.status, + } default: throw new Error(`bad module model ${(response as any).moduleModel}`) } diff --git a/app/src/redux/modules/types.ts b/app/src/redux/modules/types.ts index d78cd897cbf..5f84473a70f 100644 --- a/app/src/redux/modules/types.ts +++ b/app/src/redux/modules/types.ts @@ -3,6 +3,7 @@ import type { TemperatureModuleModel, ThermocyclerModuleModel, MagneticModuleModel, + HeaterShakerModuleModel, } from '@opentrons/shared-data' import type { Slot } from '../robot/api-types' @@ -10,6 +11,7 @@ import { TEMPERATURE_MODULE_TYPE, MAGNETIC_MODULE_TYPE, THERMOCYCLER_MODULE_TYPE, + HEATERSHAKER_MODULE_TYPE, } from '@opentrons/shared-data' import * as ApiTypes from './api-types' @@ -43,10 +45,18 @@ export interface ThermocyclerModule extends CommonModuleInfo { data: ApiTypes.ThermocyclerData } +export interface HeaterShakerModule extends CommonModuleInfo { + type: typeof HEATERSHAKER_MODULE_TYPE + model: HeaterShakerModuleModel + status: ApiTypes.HeaterShakerStatus + data: ApiTypes.HeaterShakerData +} + export type AttachedModule = | TemperatureModule | MagneticModule | ThermocyclerModule + | HeaterShakerModule // action object types export interface MatchedModule { diff --git a/components/src/icons/icon-data.ts b/components/src/icons/icon-data.ts index fd35925e644..6037d1cdf93 100644 --- a/components/src/icons/icon-data.ts +++ b/components/src/icons/icon-data.ts @@ -419,7 +419,7 @@ export const ICON_DATA_BY_NAME = { path: 'M11.6 6V5c0-2-1.6-3.6-3.6-3.6S4.4 3 4.4 5v1H3v8h10V6h-1.4zM8 11c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1zM5.6 6V5c0-1.3 1.1-2.4 2.4-2.4s2.4 1.1 2.4 2.4v1H5.6z', }, - 'heater-shaker': { + 'ot-heater-shaker': { viewBox: '0 0 16 16', path: 'M7.79174 2C8.86863 2 9.74161 2.74832 9.74161 3.67143C9.74161 5.33149 9.56715 6.8067 9.29202 7.99155C10.3317 8.52259 11.0415 9.58826 11.0415 10.8163C11.0415 12.5746 9.58643 14 7.7915 14C5.99658 14 4.5415 12.5746 4.5415 10.8163C4.5415 9.66132 5.16937 8.64997 6.109 8.09193C5.94153 6.97862 5.84187 5.5203 5.84187 3.67143C5.84187 2.74832 6.71485 2 7.79174 2ZM8.4417 3.67143C8.4417 3.36373 8.1507 3.11429 7.79174 3.11429C7.43278 3.11429 7.14178 3.36373 7.14178 3.67143V4.5H8.4417V3.67143ZM11.0378 3.23225C10.7672 3.28742 10.5926 3.55149 10.6478 3.82206C10.8156 4.64514 10.7827 5.31842 10.7108 5.77943C10.6749 6.01014 10.6292 6.18767 10.5941 6.3039C10.5765 6.36199 10.5616 6.40465 10.5519 6.43087C10.5471 6.44397 10.5435 6.45295 10.5417 6.45767L10.5403 6.46105L10.5402 6.46129L10.5402 6.46136L10.5401 6.46138C10.4324 6.71431 10.5492 7.00705 10.8019 7.11622C11.0554 7.22575 11.3497 7.10904 11.4592 6.85555L11.0002 6.65723C11.4592 6.85555 11.4593 6.85536 11.4593 6.85517L11.4595 6.85476L11.4599 6.85386L11.4608 6.85173L11.4631 6.84618L11.4698 6.82994C11.475 6.81684 11.4819 6.79922 11.49 6.7772C11.5063 6.73319 11.5276 6.67156 11.5512 6.59338C11.5985 6.43708 11.6552 6.21418 11.6989 5.93336C11.7865 5.37129 11.8223 4.57705 11.6276 3.62227C11.5724 3.3517 11.3084 3.17708 11.0378 3.23225ZM4.62176 3.82205C4.67693 3.55147 4.50231 3.2874 4.23173 3.23223C3.96116 3.17706 3.69709 3.35168 3.64192 3.62226C3.44724 4.57703 3.48305 5.37128 3.57061 5.93334C3.61436 6.21417 3.67101 6.43706 3.71829 6.59336C3.74193 6.67154 3.76326 6.73317 3.77951 6.77719C3.78763 6.7992 3.7945 6.81682 3.79975 6.82992L3.80638 6.84616L3.80871 6.85171L3.80962 6.85384L3.81001 6.85474L3.81018 6.85515C3.81027 6.85535 3.81035 6.85553 4.26934 6.65722L3.81035 6.85553C3.91988 7.10903 4.21416 7.22573 4.46766 7.11621C4.72033 7.00703 4.8371 6.7143 4.72938 6.46136L4.72937 6.46135L4.72934 6.46125L4.72924 6.46103L4.72788 6.45766C4.72598 6.45293 4.72245 6.44396 4.71762 6.43085C4.70794 6.40464 4.69304 6.36197 4.67547 6.30388C4.64032 6.18765 4.59463 6.01012 4.55869 5.77942C4.48687 5.31841 4.45393 4.64512 4.62176 3.82205ZM13.0378 3.23225C12.7672 3.28742 12.5926 3.55149 12.6478 3.82206C12.8156 4.64514 12.7827 5.31842 12.7108 5.77943C12.6749 6.01014 12.6292 6.18767 12.5941 6.3039C12.5765 6.36199 12.5616 6.40465 12.5519 6.43087C12.5471 6.44397 12.5435 6.45295 12.5417 6.45767L12.5403 6.46105L12.5402 6.46129L12.5402 6.46136L12.5401 6.46138C12.4324 6.71431 12.5492 7.00705 12.8019 7.11622C13.0554 7.22575 13.3497 7.10904 13.4592 6.85555L13.0002 6.65723C13.4592 6.85555 13.4593 6.85536 13.4593 6.85517L13.4595 6.85476L13.4599 6.85386L13.4608 6.85173L13.4631 6.84618L13.4698 6.82994C13.475 6.81684 13.4819 6.79922 13.49 6.7772C13.5063 6.73319 13.5276 6.67156 13.5512 6.59338C13.5985 6.43708 13.6552 6.21418 13.6989 5.93336C13.7865 5.37129 13.8223 4.57705 13.6276 3.62227C13.5724 3.3517 13.3084 3.17708 13.0378 3.23225ZM2.62176 3.82205C2.67693 3.55147 2.50231 3.2874 2.23173 3.23223C1.96116 3.17706 1.69709 3.35168 1.64192 3.62226C1.44724 4.57703 1.48305 5.37128 1.57061 5.93334C1.61436 6.21417 1.67101 6.43706 1.71828 6.59336C1.74193 6.67154 1.76326 6.73317 1.77951 6.77719C1.78763 6.7992 1.7945 6.81682 1.79975 6.82992L1.80638 6.84616L1.80871 6.85171L1.80962 6.85384L1.81001 6.85474L1.81018 6.85515C1.81027 6.85535 1.81035 6.85553 2.26934 6.65722L1.81035 6.85553C1.91988 7.10903 2.21416 7.22573 2.46766 7.11621C2.72033 7.00703 2.8371 6.7143 2.72938 6.46136L2.72937 6.46135L2.72934 6.46125L2.72924 6.46103L2.72788 6.45766C2.72598 6.45293 2.72245 6.44396 2.71762 6.43085C2.70794 6.40464 2.69304 6.36197 2.67547 6.30388C2.64032 6.18765 2.59463 6.01012 2.55869 5.77942C2.48687 5.31841 2.45393 4.64512 2.62176 3.82205Z', diff --git a/robot-server/robot_server/service/legacy/models/modules.py b/robot-server/robot_server/service/legacy/models/modules.py index a514fc3ca8a..39d119777dd 100644 --- a/robot-server/robot_server/service/legacy/models/modules.py +++ b/robot-server/robot_server/service/legacy/models/modules.py @@ -247,7 +247,7 @@ class Config: "fwVersion": "0.0.1", "hasAvailableUpdate": True, "model": "heater-shaker_v10", - "moduleModel": "heaterShakerV1", + "moduleModel": "heaterShakerModuleV1", "port": "/dev/ot_module_heatershaker1", "usbPort": {"hub": None, "port": None}, "revision": "heater-shaker_v10", diff --git a/robot-server/tests/integration/test_modules.tavern.yaml b/robot-server/tests/integration/test_modules.tavern.yaml index 8b3352fe146..4da242468a8 100644 --- a/robot-server/tests/integration/test_modules.tavern.yaml +++ b/robot-server/tests/integration/test_modules.tavern.yaml @@ -37,7 +37,7 @@ stages: totalStepCount: Null - name: heatershaker displayName: heatershaker - moduleModel: heaterShakerV1 + moduleModel: heaterShakerModuleV1 port: !anystr usbPort: !anydict serial: !anystr diff --git a/robot-server/tests/service/legacy/routers/test_modules.py b/robot-server/tests/service/legacy/routers/test_modules.py index 4630a628825..db8a1d57ae9 100644 --- a/robot-server/tests/service/legacy/routers/test_modules.py +++ b/robot-server/tests/service/legacy/routers/test_modules.py @@ -246,7 +246,7 @@ def test_get_module_heater_shaker(api_client, hardware, heater_shaker) -> None: "fwVersion": "dummyVersionHS", "hasAvailableUpdate": False, "model": "dummyModelHS", - "moduleModel": "heaterShakerV1", + "moduleModel": "heaterShakerModuleV1", "name": "heatershaker", "port": "/dev/ot_module_heatershaker1", "usbPort": {"hub": None, "port": None}, diff --git a/shared-data/js/constants.ts b/shared-data/js/constants.ts index 9d4be85ab30..baa365d5fdf 100644 --- a/shared-data/js/constants.ts +++ b/shared-data/js/constants.ts @@ -34,6 +34,8 @@ export const TEMPERATURE_MODULE_V2: 'temperatureModuleV2' = 'temperatureModuleV2' export const THERMOCYCLER_MODULE_V1: 'thermocyclerModuleV1' = 'thermocyclerModuleV1' +export const HEATERSHAKER_MODULE_V1: 'heaterShakerModuleV1' = + 'heaterShakerModuleV1' // pipette display categories export const GEN2: 'GEN2' = 'GEN2' @@ -52,6 +54,8 @@ export const TEMPERATURE_MODULE_TYPE: 'temperatureModuleType' = export const MAGNETIC_MODULE_TYPE: 'magneticModuleType' = 'magneticModuleType' export const THERMOCYCLER_MODULE_TYPE: 'thermocyclerModuleType' = 'thermocyclerModuleType' +export const HEATERSHAKER_MODULE_TYPE: 'heaterShakerModuleType' = + 'heaterShakerModuleType' export const MAGNETIC_MODULE_MODELS = [MAGNETIC_MODULE_V1, MAGNETIC_MODULE_V2] @@ -62,16 +66,20 @@ export const TEMPERATURE_MODULE_MODELS = [ export const THERMOCYCLER_MODULE_MODELS = [THERMOCYCLER_MODULE_V1] +export const HEATERSHAKER_MODULE_MODELS = [HEATERSHAKER_MODULE_V1] + export const MODULE_MODELS = [ ...MAGNETIC_MODULE_MODELS, ...TEMPERATURE_MODULE_MODELS, ...THERMOCYCLER_MODULE_MODELS, + ...HEATERSHAKER_MODULE_MODELS, ] export const MODULE_TYPES = [ TEMPERATURE_MODULE_TYPE, MAGNETIC_MODULE_TYPE, THERMOCYCLER_MODULE_TYPE, + HEATERSHAKER_MODULE_TYPE, ] export const GEN_ONE_MULTI_PIPETTES = ['p10_multi', 'p50_multi', 'p300_multi'] diff --git a/shared-data/js/modules.ts b/shared-data/js/modules.ts index 9b441893b2d..064f05b9515 100644 --- a/shared-data/js/modules.ts +++ b/shared-data/js/modules.ts @@ -3,6 +3,7 @@ import magneticModuleV2 from '../module/definitions/3/magneticModuleV2.json' import temperatureModuleV1 from '../module/definitions/3/temperatureModuleV1.json' import temperatureModuleV2 from '../module/definitions/3/temperatureModuleV2.json' import thermocyclerModuleV1 from '../module/definitions/3/thermocyclerModuleV1.json' +import heaterShakerModuleV1 from '../module/definitions/3/heaterShakerModuleV1.json' import { MAGDECK, @@ -13,6 +14,7 @@ import { TEMPERATURE_MODULE_V2, THERMOCYCLER, THERMOCYCLER_MODULE_V1, + HEATERSHAKER_MODULE_V1, } from './constants' import type { @@ -41,6 +43,10 @@ export const getModuleDef2 = (moduleModel: ModuleModel): ModuleDefinition => { case THERMOCYCLER_MODULE_V1: return thermocyclerModuleV1 as ModuleDefinition + case HEATERSHAKER_MODULE_V1: + // @ts-expect-error: remove this once 2D render key is added to heatershaker module definition + return heaterShakerModuleV1 as ModuleDefinition + default: throw new Error(`Invalid module model ${moduleModel as string}`) } diff --git a/shared-data/js/types.ts b/shared-data/js/types.ts index f377562e854..0e63727489e 100644 --- a/shared-data/js/types.ts +++ b/shared-data/js/types.ts @@ -7,9 +7,11 @@ import { TEMPERATURE_MODULE_V1, TEMPERATURE_MODULE_V2, THERMOCYCLER_MODULE_V1, + HEATERSHAKER_MODULE_V1, MAGNETIC_MODULE_TYPE, TEMPERATURE_MODULE_TYPE, THERMOCYCLER_MODULE_TYPE, + HEATERSHAKER_MODULE_TYPE, GEN1, GEN2, LEFT, @@ -167,6 +169,7 @@ export type ModuleType = | typeof MAGNETIC_MODULE_TYPE | typeof TEMPERATURE_MODULE_TYPE | typeof THERMOCYCLER_MODULE_TYPE + | typeof HEATERSHAKER_MODULE_TYPE // ModuleModel corresponds to top-level keys in shared-data/module/definitions/2 export type MagneticModuleModel = @@ -179,10 +182,13 @@ export type TemperatureModuleModel = export type ThermocyclerModuleModel = typeof THERMOCYCLER_MODULE_V1 +export type HeaterShakerModuleModel = typeof HEATERSHAKER_MODULE_V1 + export type ModuleModel = | MagneticModuleModel | TemperatureModuleModel | ThermocyclerModuleModel + | HeaterShakerModuleModel export type ModuleModelWithLegacy = | ModuleModel From 976619ad03fe033fd0df424203357a8f5efd36dd Mon Sep 17 00:00:00 2001 From: Sakib Hossain Date: Fri, 25 Feb 2022 14:25:48 -0500 Subject: [PATCH 2/5] update snapshot --- .../__snapshots__/icons.test.tsx.snap | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/components/src/__tests__/__snapshots__/icons.test.tsx.snap b/components/src/__tests__/__snapshots__/icons.test.tsx.snap index ae216168de0..5898629a6a3 100644 --- a/components/src/__tests__/__snapshots__/icons.test.tsx.snap +++ b/components/src/__tests__/__snapshots__/icons.test.tsx.snap @@ -1301,6 +1301,30 @@ exports[`icons ot-flat-bottom renders correctly 1`] = ` `; +exports[`icons ot-heater-shaker renders correctly 1`] = ` +.c0.spin { + -webkit-animation: GLFYz 0.8s steps(8) infinite; + animation: GLFYz 0.8s steps(8) infinite; + -webkit-transform-origin: center; + -ms-transform-origin: center; + transform-origin: center; +} + + +`; + exports[`icons ot-logo renders correctly 1`] = ` .c0.spin { -webkit-animation: GLFYz 0.8s steps(8) infinite; From dc31c385d19e156b24eee4a40e540d123ed40686 Mon Sep 17 00:00:00 2001 From: Sakib Hossain Date: Fri, 25 Feb 2022 14:34:44 -0500 Subject: [PATCH 3/5] fix icon test --- .../__snapshots__/icons.test.tsx.snap | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/components/src/__tests__/__snapshots__/icons.test.tsx.snap b/components/src/__tests__/__snapshots__/icons.test.tsx.snap index 5898629a6a3..5911072c1cd 100644 --- a/components/src/__tests__/__snapshots__/icons.test.tsx.snap +++ b/components/src/__tests__/__snapshots__/icons.test.tsx.snap @@ -773,30 +773,6 @@ exports[`icons folder-open renders correctly 1`] = ` `; -exports[`icons heater-shaker renders correctly 1`] = ` -.c0.spin { - -webkit-animation: GLFYz 0.8s steps(8) infinite; - animation: GLFYz 0.8s steps(8) infinite; - -webkit-transform-origin: center; - -ms-transform-origin: center; - transform-origin: center; -} - - -`; - exports[`icons help-circle renders correctly 1`] = ` .c0.spin { -webkit-animation: GLFYz 0.8s steps(8) infinite; From b89e0ad90c4e505ac00cd5dfc562ce6a8a96e901 Mon Sep 17 00:00:00 2001 From: Sakib Hossain Date: Mon, 28 Feb 2022 16:00:28 -0500 Subject: [PATCH 4/5] fix typechecks --- .../images/modules/heaterShakerModuleV1@3x.png | Bin 0 -> 29683 bytes .../Devices/ModuleCard/ModuleOverflowMenu.tsx | 8 ++++++++ app/src/organisms/Devices/ModuleCard/index.tsx | 6 +++++- .../ModuleSettings/ModuleItem/ModuleImage.tsx | 1 + .../Robots/ModuleSettings/ModuleItem/index.tsx | 15 ++++++++++----- .../LabwareSelectionModal.tsx | 3 +++ .../modals/FilePipettesModal/ModuleFields.tsx | 6 ++++++ .../__tests__/ModuleFields.test.tsx | 12 ++++++++++-- .../FilePipettesModal/__tests__/index.test.tsx | 6 ++++++ .../modals/FilePipettesModal/index.tsx | 6 ++++++ .../src/components/modules/ModuleDiagram.tsx | 5 +++++ .../modules/__tests__/EditModulesCard.test.tsx | 2 +- protocol-designer/src/constants.ts | 9 +++++++++ .../src/images/modules/heatershaker.png | Bin 0 -> 29683 bytes protocol-designer/src/modules/moduleData.ts | 8 ++++++++ protocol-designer/src/step-forms/types.ts | 1 + .../src/utils/labwareModuleCompatibility.ts | 3 +++ shared-data/js/helpers/getModuleVizDims.ts | 12 ++++++++++++ 18 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 app/src/assets/images/modules/heaterShakerModuleV1@3x.png create mode 100644 protocol-designer/src/images/modules/heatershaker.png diff --git a/app/src/assets/images/modules/heaterShakerModuleV1@3x.png b/app/src/assets/images/modules/heaterShakerModuleV1@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..abcecab1c6ff6080f594bf130eebbd6619692cee GIT binary patch literal 29683 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 GhW|gb;ay|^ literal 0 HcmV?d00001 diff --git a/app/src/organisms/Devices/ModuleCard/ModuleOverflowMenu.tsx b/app/src/organisms/Devices/ModuleCard/ModuleOverflowMenu.tsx index 22e08cac47f..c239082c55b 100644 --- a/app/src/organisms/Devices/ModuleCard/ModuleOverflowMenu.tsx +++ b/app/src/organisms/Devices/ModuleCard/ModuleOverflowMenu.tsx @@ -44,6 +44,14 @@ export const ModuleOverflowMenu = ( isSecondary: false, }, ], + // TODO(sh, 2022-02-28): add heater shaker menu items + heaterShakerModuleType: [ + { + setSetting: 'Start', + turnOffSetting: 'Deactivate', + isSecondary: true, + }, + ], } const AboutModuleBtn = ( diff --git a/app/src/organisms/Devices/ModuleCard/index.tsx b/app/src/organisms/Devices/ModuleCard/index.tsx index 16c39d71021..2196687e778 100644 --- a/app/src/organisms/Devices/ModuleCard/index.tsx +++ b/app/src/organisms/Devices/ModuleCard/index.tsx @@ -22,6 +22,7 @@ import { import { getModuleDisplayName, MAGNETIC_MODULE_TYPE, + TEMPERATURE_MODULE_TYPE, THERMOCYCLER_MODULE_TYPE, } from '@opentrons/shared-data' import { OverflowBtn } from '../../../atoms/MenuList/OverflowBtn' @@ -200,7 +201,7 @@ const ModuleSlideout = (props: ModuleSlideoutProps): JSX.Element => { isExpanded={showSlideout} /> ) - } else { + } else if (module.type === TEMPERATURE_MODULE_TYPE) { return ( { isExpanded={showSlideout} /> ) + } else { + // TODO(sh, 2022-02-28): render heater shaker slideout + return
Heater Shaker Slide
} } diff --git a/app/src/pages/Robots/ModuleSettings/ModuleItem/ModuleImage.tsx b/app/src/pages/Robots/ModuleSettings/ModuleItem/ModuleImage.tsx index 1bb58f08cca..10cd85abae3 100644 --- a/app/src/pages/Robots/ModuleSettings/ModuleItem/ModuleImage.tsx +++ b/app/src/pages/Robots/ModuleSettings/ModuleItem/ModuleImage.tsx @@ -34,4 +34,5 @@ const MODULE_IMGS: { [m in ModuleModel]: string } = { magneticModuleV1: require('../../../../assets/images/modules/magneticModuleV1@3x.png'), magneticModuleV2: require('../../../../assets/images/modules/magneticModuleV2@3x.png'), thermocyclerModuleV1: require('../../../../assets/images/modules/thermocyclerModuleV1@3x.png'), + heaterShakerModuleV1: require('../../../../assets/images/modules/heaterShakerModuleV1@3x.png'), } diff --git a/app/src/pages/Robots/ModuleSettings/ModuleItem/index.tsx b/app/src/pages/Robots/ModuleSettings/ModuleItem/index.tsx index 9c94fbf6281..db9901ddae0 100644 --- a/app/src/pages/Robots/ModuleSettings/ModuleItem/index.tsx +++ b/app/src/pages/Robots/ModuleSettings/ModuleItem/index.tsx @@ -17,7 +17,10 @@ import { ModuleImage } from './ModuleImage' import { ModuleUpdate } from './ModuleUpdate' import { ModuleControls } from '../../../../molecules/ModuleControls' import type { AttachedModule } from '../../../../redux/modules/types' -import { getModuleDisplayName } from '@opentrons/shared-data' +import { + getModuleDisplayName, + HEATERSHAKER_MODULE_TYPE, +} from '@opentrons/shared-data' interface Props { module: AttachedModule @@ -43,10 +46,12 @@ export function ModuleItem(props: Props): JSX.Element { - + {module.type !== HEATERSHAKER_MODULE_TYPE && ( + + )} diff --git a/protocol-designer/src/components/LabwareSelectionModal/LabwareSelectionModal.tsx b/protocol-designer/src/components/LabwareSelectionModal/LabwareSelectionModal.tsx index 73aa2337754..bc73c41303e 100644 --- a/protocol-designer/src/components/LabwareSelectionModal/LabwareSelectionModal.tsx +++ b/protocol-designer/src/components/LabwareSelectionModal/LabwareSelectionModal.tsx @@ -13,6 +13,7 @@ import { TEMPERATURE_MODULE_TYPE, MAGNETIC_MODULE_TYPE, THERMOCYCLER_MODULE_TYPE, + HEATERSHAKER_MODULE_TYPE, LabwareDefinition2, ModuleType, } from '@opentrons/shared-data' @@ -71,6 +72,8 @@ const RECOMMENDED_LABWARE_BY_MODULE: { [K in ModuleType]: string[] } = { ], [MAGNETIC_MODULE_TYPE]: ['nest_96_wellplate_100ul_pcr_full_skirt'], [THERMOCYCLER_MODULE_TYPE]: ['nest_96_wellplate_100ul_pcr_full_skirt'], + // TODO(sh, 2022-02-28): add list of recommended labware for the heater shaker + [HEATERSHAKER_MODULE_TYPE]: [], } export const getLabwareIsRecommended = ( diff --git a/protocol-designer/src/components/modals/FilePipettesModal/ModuleFields.tsx b/protocol-designer/src/components/modals/FilePipettesModal/ModuleFields.tsx index 51d89e89cba..e95d4c79519 100644 --- a/protocol-designer/src/components/modals/FilePipettesModal/ModuleFields.tsx +++ b/protocol-designer/src/components/modals/FilePipettesModal/ModuleFields.tsx @@ -27,6 +27,9 @@ export interface ModuleFieldsProps { thermocyclerModuleType?: { model: string } + heaterShakerModuleType?: { + model: string + } } touched: | null @@ -41,6 +44,9 @@ export interface ModuleFieldsProps { thermocyclerModuleType?: { model: boolean } + heaterShakerModuleType?: { + model: boolean + } } values: FormModulesByType onFieldChange: (event: React.ChangeEvent) => unknown diff --git a/protocol-designer/src/components/modals/FilePipettesModal/__tests__/ModuleFields.test.tsx b/protocol-designer/src/components/modals/FilePipettesModal/__tests__/ModuleFields.test.tsx index da9159a7220..8b5a8758c5e 100644 --- a/protocol-designer/src/components/modals/FilePipettesModal/__tests__/ModuleFields.test.tsx +++ b/protocol-designer/src/components/modals/FilePipettesModal/__tests__/ModuleFields.test.tsx @@ -5,6 +5,7 @@ import { THERMOCYCLER_MODULE_TYPE, TEMPERATURE_MODULE_TYPE, MAGNETIC_MODULE_TYPE, + HEATERSHAKER_MODULE_TYPE, } from '@opentrons/shared-data' import { CheckboxField } from '@opentrons/components' import { DEFAULT_MODEL_FOR_MODULE_TYPE } from '../../../../constants' @@ -14,7 +15,8 @@ import { ModuleFields, ModuleFieldsProps } from '../ModuleFields' describe('ModuleFields', () => { let magnetModuleOnDeck, temperatureModuleNotOnDeck, - thermocyclerModuleNotOnDeck + thermocyclerModuleNotOnDeck, + heaterShakerModuleNotOnDeck let props: ModuleFieldsProps beforeEach(() => { magnetModuleOnDeck = { @@ -32,12 +34,18 @@ describe('ModuleFields', () => { slot: '9', model: null, } + heaterShakerModuleNotOnDeck = { + onDeck: false, + slot: '6', + model: null, + } props = { values: { [MAGNETIC_MODULE_TYPE]: magnetModuleOnDeck, [TEMPERATURE_MODULE_TYPE]: temperatureModuleNotOnDeck, [THERMOCYCLER_MODULE_TYPE]: thermocyclerModuleNotOnDeck, + [HEATERSHAKER_MODULE_TYPE]: heaterShakerModuleNotOnDeck, }, onFieldChange: jest.fn(), onSetFieldValue: jest.fn(), @@ -50,7 +58,7 @@ describe('ModuleFields', () => { it('renders a module selection element for every module', () => { const wrapper = shallow() - expect(wrapper.find(CheckboxField)).toHaveLength(3) + expect(wrapper.find(CheckboxField)).toHaveLength(4) }) it('adds module to protocol when checkbox is selected and resets the model field', () => { diff --git a/protocol-designer/src/components/modals/FilePipettesModal/__tests__/index.test.tsx b/protocol-designer/src/components/modals/FilePipettesModal/__tests__/index.test.tsx index e4c38e35a9f..14a6c6831be 100644 --- a/protocol-designer/src/components/modals/FilePipettesModal/__tests__/index.test.tsx +++ b/protocol-designer/src/components/modals/FilePipettesModal/__tests__/index.test.tsx @@ -5,6 +5,7 @@ import { MAGNETIC_MODULE_TYPE, TEMPERATURE_MODULE_TYPE, THERMOCYCLER_MODULE_TYPE, + HEATERSHAKER_MODULE_TYPE, MAGNETIC_MODULE_V1, MAGNETIC_MODULE_V2, } from '@opentrons/shared-data' @@ -51,6 +52,11 @@ describe('FilePipettesModal', () => { slot: '', model: null, }, + [HEATERSHAKER_MODULE_TYPE]: { + onDeck: false, + slot: '', + model: null, + }, } props = { diff --git a/protocol-designer/src/components/modals/FilePipettesModal/index.tsx b/protocol-designer/src/components/modals/FilePipettesModal/index.tsx index 137bcea1b00..12396540c5c 100644 --- a/protocol-designer/src/components/modals/FilePipettesModal/index.tsx +++ b/protocol-designer/src/components/modals/FilePipettesModal/index.tsx @@ -22,6 +22,7 @@ import { TEMPERATURE_MODULE_TYPE, THERMOCYCLER_MODULE_TYPE, THERMOCYCLER_MODULE_V1, + HEATERSHAKER_MODULE_TYPE, ModuleType, ModuleModel, } from '@opentrons/shared-data' @@ -95,6 +96,11 @@ const initialFormState: FormState = { model: THERMOCYCLER_MODULE_V1, // Default to GEN1 for TC only slot: SPAN7_8_10_11_SLOT, }, + [HEATERSHAKER_MODULE_TYPE]: { + onDeck: false, + model: null, + slot: '6', + }, }, } diff --git a/protocol-designer/src/components/modules/ModuleDiagram.tsx b/protocol-designer/src/components/modules/ModuleDiagram.tsx index b654e3d89bd..0b43899ef1f 100644 --- a/protocol-designer/src/components/modules/ModuleDiagram.tsx +++ b/protocol-designer/src/components/modules/ModuleDiagram.tsx @@ -11,6 +11,8 @@ import { TEMPERATURE_MODULE_V2, THERMOCYCLER_MODULE_V1, ModuleModel, + HEATERSHAKER_MODULE_TYPE, + HEATERSHAKER_MODULE_V1, } from '@opentrons/shared-data' interface Props { @@ -36,6 +38,9 @@ const MODULE_IMG_BY_TYPE: ModuleImg = { [THERMOCYCLER_MODULE_TYPE]: { [THERMOCYCLER_MODULE_V1]: require('../../images/modules/thermocycler.jpg'), }, + [HEATERSHAKER_MODULE_TYPE]: { + [HEATERSHAKER_MODULE_V1]: require('../../images/modules/heatershaker.png'), + }, } export function ModuleDiagram(props: Props): JSX.Element { diff --git a/protocol-designer/src/components/modules/__tests__/EditModulesCard.test.tsx b/protocol-designer/src/components/modules/__tests__/EditModulesCard.test.tsx index 8165b5aa38c..d10de42cd67 100644 --- a/protocol-designer/src/components/modules/__tests__/EditModulesCard.test.tsx +++ b/protocol-designer/src/components/modules/__tests__/EditModulesCard.test.tsx @@ -184,7 +184,7 @@ describe('EditModulesCard', () => { it('displays module row with module to add when no moduleData', () => { const wrapper = render(props) - expect(wrapper.find(ModuleRow)).toHaveLength(3) + expect(wrapper.find(ModuleRow)).toHaveLength(4) SUPPORTED_MODULE_TYPES.forEach(moduleType => { expect( wrapper.find(ModuleRow).filter({ type: moduleType }).props() diff --git a/protocol-designer/src/constants.ts b/protocol-designer/src/constants.ts index 40101d5873e..06b5774fe3d 100644 --- a/protocol-designer/src/constants.ts +++ b/protocol-designer/src/constants.ts @@ -3,11 +3,13 @@ import { MAGNETIC_MODULE_TYPE, TEMPERATURE_MODULE_TYPE, THERMOCYCLER_MODULE_TYPE, + HEATERSHAKER_MODULE_TYPE, MAGNETIC_MODULE_V1, MAGNETIC_MODULE_V2, TEMPERATURE_MODULE_V1, TEMPERATURE_MODULE_V2, THERMOCYCLER_MODULE_V1, + HEATERSHAKER_MODULE_V1, LabwareDefinition2, DeckSlot as DeckDefSlot, ModuleType, @@ -110,11 +112,18 @@ export const MODELS_FOR_MODULE_TYPE: Record< value: THERMOCYCLER_MODULE_V1 as string, }, ], + [HEATERSHAKER_MODULE_TYPE]: [ + { + name: i18n.t(`modules.model_display_name.${HEATERSHAKER_MODULE_V1}`), + value: HEATERSHAKER_MODULE_V1 as string, + }, + ], } export const DEFAULT_MODEL_FOR_MODULE_TYPE: Record = { [MAGNETIC_MODULE_TYPE]: MAGNETIC_MODULE_V1, [TEMPERATURE_MODULE_TYPE]: TEMPERATURE_MODULE_V1, [THERMOCYCLER_MODULE_TYPE]: THERMOCYCLER_MODULE_V1, + [HEATERSHAKER_MODULE_TYPE]: HEATERSHAKER_MODULE_V1, } // Values for pauseAction field export const PAUSE_UNTIL_RESUME: 'untilResume' = 'untilResume' diff --git a/protocol-designer/src/images/modules/heatershaker.png b/protocol-designer/src/images/modules/heatershaker.png new file mode 100644 index 0000000000000000000000000000000000000000..abcecab1c6ff6080f594bf130eebbd6619692cee GIT binary patch literal 29683 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 GhW|gb;ay|^ literal 0 HcmV?d00001 diff --git a/protocol-designer/src/modules/moduleData.ts b/protocol-designer/src/modules/moduleData.ts index 2794affe55c..9912300014b 100644 --- a/protocol-designer/src/modules/moduleData.ts +++ b/protocol-designer/src/modules/moduleData.ts @@ -3,6 +3,7 @@ import { MAGNETIC_MODULE_TYPE, TEMPERATURE_MODULE_TYPE, THERMOCYCLER_MODULE_TYPE, + HEATERSHAKER_MODULE_TYPE, ModuleType, } from '@opentrons/shared-data' import { DropdownOption } from '@opentrons/components' @@ -10,6 +11,7 @@ export const SUPPORTED_MODULE_TYPES: ModuleType[] = [ MAGNETIC_MODULE_TYPE, TEMPERATURE_MODULE_TYPE, THERMOCYCLER_MODULE_TYPE, + HEATERSHAKER_MODULE_TYPE, ] type SupportedSlotMap = Record export const SUPPORTED_MODULE_SLOTS: SupportedSlotMap = { @@ -31,6 +33,12 @@ export const SUPPORTED_MODULE_SLOTS: SupportedSlotMap = { value: SPAN7_8_10_11_SLOT, }, ], + [HEATERSHAKER_MODULE_TYPE]: [ + { + name: 'Slot 6 (supported)', + value: '6', + }, + ], } export const ALL_MODULE_SLOTS: DropdownOption[] = [ { diff --git a/protocol-designer/src/step-forms/types.ts b/protocol-designer/src/step-forms/types.ts index c7bac582bc0..daf1c6bcff3 100644 --- a/protocol-designer/src/step-forms/types.ts +++ b/protocol-designer/src/step-forms/types.ts @@ -32,6 +32,7 @@ export interface FormModulesByType { magneticModuleType: FormModule temperatureModuleType: FormModule thermocyclerModuleType: FormModule + heaterShakerModuleType: FormModule } export type ModuleEntities = Record // NOTE: semi-redundant 'type' key in FooModuleState types is required for Flow to disambiguate 'moduleState' diff --git a/protocol-designer/src/utils/labwareModuleCompatibility.ts b/protocol-designer/src/utils/labwareModuleCompatibility.ts index acbebbb8880..59f497c8afb 100644 --- a/protocol-designer/src/utils/labwareModuleCompatibility.ts +++ b/protocol-designer/src/utils/labwareModuleCompatibility.ts @@ -6,6 +6,7 @@ import { THERMOCYCLER_MODULE_TYPE, LabwareDefinition2, ModuleType, + HEATERSHAKER_MODULE_TYPE, } from '@opentrons/shared-data' import { LabwareDefByDefURI } from '../labware-defs' import { LabwareOnDeck } from '../step-forms' @@ -50,6 +51,8 @@ const COMPATIBLE_LABWARE_ALLOWLIST_BY_MODULE_TYPE: Record< 'biorad_96_wellplate_200ul_pcr', 'nest_96_wellplate_100ul_pcr_full_skirt', ], + // TODO(sh, 2022-02-28): add list of compatible labware for the heater shaker + [HEATERSHAKER_MODULE_TYPE]: [], } export const getLabwareIsCompatible = ( def: LabwareDefinition2, diff --git a/shared-data/js/helpers/getModuleVizDims.ts b/shared-data/js/helpers/getModuleVizDims.ts index 16b6e5b535f..0ca2d29a74c 100644 --- a/shared-data/js/helpers/getModuleVizDims.ts +++ b/shared-data/js/helpers/getModuleVizDims.ts @@ -4,6 +4,7 @@ import { MAGNETIC_MODULE_TYPE, TEMPERATURE_MODULE_TYPE, THERMOCYCLER_MODULE_TYPE, + HEATERSHAKER_MODULE_TYPE, STD_SLOT_X_DIM as SLOT_X, STD_SLOT_Y_DIM as SLOT_Y, STD_SLOT_DIVIDER_WIDTH as DIVIDER, @@ -53,6 +54,17 @@ const MODULE_VIZ_DIMS: Record = { childXDimension: SLOT_X, childYDimension: SLOT_Y, }, + // TODO(sh, 2022-02-28): This is stubbed out using magdeck dimensions, all of this logic should be deprecated + [HEATERSHAKER_MODULE_TYPE]: { + xOffset: -1 * (SLOT_X * 0.2 + DIVIDER), + yOffset: -1 * DIVIDER, + xDimension: SLOT_X * 1.2 + DIVIDER * 2, + yDimension: SLOT_Y + DIVIDER * 2, + childXOffset: 0, + childYOffset: 0, + childXDimension: SLOT_X, + childYDimension: SLOT_Y, + }, } export const getModuleVizDims = ( orientation: ModuleOrientation, From 2da92efe8264f8fd8e1d941a6675965f5ff8aedf Mon Sep 17 00:00:00 2001 From: Sakib Hossain Date: Tue, 1 Mar 2022 14:59:18 -0500 Subject: [PATCH 5/5] hide module behind ff --- .../modals/FilePipettesModal/ModuleFields.tsx | 9 ++- .../__tests__/ModuleFields.test.tsx | 55 +++++++++++++++---- .../components/modules/EditModulesCard.tsx | 12 +++- .../__tests__/EditModulesCard.test.tsx | 4 ++ .../src/localization/en/modules.json | 9 ++- 5 files changed, 72 insertions(+), 17 deletions(-) diff --git a/protocol-designer/src/components/modals/FilePipettesModal/ModuleFields.tsx b/protocol-designer/src/components/modals/FilePipettesModal/ModuleFields.tsx index e95d4c79519..951dcf63cd0 100644 --- a/protocol-designer/src/components/modals/FilePipettesModal/ModuleFields.tsx +++ b/protocol-designer/src/components/modals/FilePipettesModal/ModuleFields.tsx @@ -1,4 +1,5 @@ import * as React from 'react' +import { useSelector } from 'react-redux' import { CheckboxField, DropdownField, FormGroup } from '@opentrons/components' import { i18n } from '../../../localization' import { @@ -6,6 +7,7 @@ import { MODELS_FOR_MODULE_TYPE, } from '../../../constants' import { ModuleDiagram } from '../../modules' +import { selectors as featureFlagSelectors } from '../../../feature-flags' import styles from './FilePipettesModal.css' @@ -65,8 +67,13 @@ export function ModuleFields(props: ModuleFieldsProps): JSX.Element { errors, touched, } = props + const enableHeaterShaker = useSelector( + featureFlagSelectors.getEnabledHeaterShaker + ) // @ts-expect-error(sa, 2021-6-21): Object.keys not smart enough to take the keys of FormModulesByType - const modules: ModuleType[] = Object.keys(values) + const modules: ModuleType[] = enableHeaterShaker + ? Object.keys(values) + : Object.keys(values).filter(module => module !== 'heaterShakerModuleType') const handleOnDeckChange = (type: ModuleType) => (e: React.ChangeEvent) => { const targetToClear = `modulesByType.${type}.model` diff --git a/protocol-designer/src/components/modals/FilePipettesModal/__tests__/ModuleFields.test.tsx b/protocol-designer/src/components/modals/FilePipettesModal/__tests__/ModuleFields.test.tsx index 8b5a8758c5e..10b71f09642 100644 --- a/protocol-designer/src/components/modals/FilePipettesModal/__tests__/ModuleFields.test.tsx +++ b/protocol-designer/src/components/modals/FilePipettesModal/__tests__/ModuleFields.test.tsx @@ -1,5 +1,6 @@ import React from 'react' -import { shallow } from 'enzyme' +import { Provider } from 'react-redux' +import { mount } from 'enzyme' import { MAGNETIC_MODULE_V2, THERMOCYCLER_MODULE_TYPE, @@ -11,6 +12,13 @@ import { CheckboxField } from '@opentrons/components' import { DEFAULT_MODEL_FOR_MODULE_TYPE } from '../../../../constants' import { ModuleDiagram } from '../../../modules' import { ModuleFields, ModuleFieldsProps } from '../ModuleFields' +import { selectors as featureFlagSelectors } from '../../../../feature-flags' + +jest.mock('../../../../feature-flags') + +const getEnabledHeaterShakerMock = featureFlagSelectors.getEnabledHeaterShaker as jest.MockedFunction< + typeof featureFlagSelectors.getEnabledHeaterShaker +> describe('ModuleFields', () => { let magnetModuleOnDeck, @@ -18,7 +26,14 @@ describe('ModuleFields', () => { thermocyclerModuleNotOnDeck, heaterShakerModuleNotOnDeck let props: ModuleFieldsProps + let store: any beforeEach(() => { + store = { + dispatch: jest.fn(), + subscribe: jest.fn(), + getState: () => ({}), + } + magnetModuleOnDeck = { onDeck: true, slot: '1', @@ -54,11 +69,22 @@ describe('ModuleFields', () => { touched: null, errors: null, } + + getEnabledHeaterShakerMock.mockReturnValue(false) }) + + function render(props: ModuleFieldsProps) { + return mount( + + + + ) + } + it('renders a module selection element for every module', () => { - const wrapper = shallow() + const wrapper = render(props) - expect(wrapper.find(CheckboxField)).toHaveLength(4) + expect(wrapper.find(CheckboxField)).toHaveLength(3) }) it('adds module to protocol when checkbox is selected and resets the model field', () => { @@ -71,9 +97,10 @@ describe('ModuleFields', () => { }, } - const wrapper = shallow() + const wrapper = render(props) const temperatureSelectChange = wrapper .find({ name: checkboxTargetName }) + .first() .prop('onChange') temperatureSelectChange(expectedEvent) @@ -91,10 +118,12 @@ describe('ModuleFields', () => { }, } - const wrapper = shallow() - const magnetModelSelect = wrapper.find({ - name: magnetModelName, - }) + const wrapper = render(props) + const magnetModelSelect = wrapper + .find({ + name: magnetModelName, + }) + .first() magnetModelSelect.prop('onChange')(expectedEvent) expect(magnetModelSelect).toHaveLength(1) @@ -117,14 +146,16 @@ describe('ModuleFields', () => { } const magnetModelSelectName = `modulesByType.${MAGNETIC_MODULE_TYPE}.model` - const wrapper = shallow() - const magnetModelSelect = wrapper.find({ name: magnetModelSelectName }) + const wrapper = render(propsWithErrors) + const magnetModelSelect = wrapper + .find({ name: magnetModelSelectName }) + .first() expect(magnetModelSelect.prop('error')).toEqual('required') }) it('displays a default module img when no model has been selected', () => { - const wrapper = shallow() + const wrapper = render(props) const temperatureModuleDiagramProps = wrapper .find(ModuleDiagram) .filter({ type: TEMPERATURE_MODULE_TYPE }) @@ -139,7 +170,7 @@ describe('ModuleFields', () => { it('displays specific module img when model has been selected', () => { props.values[MAGNETIC_MODULE_TYPE].model = MAGNETIC_MODULE_V2 - const wrapper = shallow() + const wrapper = render(props) const magnetModuleDiagramProps = wrapper .find(ModuleDiagram) .filter({ type: MAGNETIC_MODULE_TYPE }) diff --git a/protocol-designer/src/components/modules/EditModulesCard.tsx b/protocol-designer/src/components/modules/EditModulesCard.tsx index d2d97a9dcbc..1818f7455f1 100644 --- a/protocol-designer/src/components/modules/EditModulesCard.tsx +++ b/protocol-designer/src/components/modules/EditModulesCard.tsx @@ -26,6 +26,10 @@ export interface Props { export function EditModulesCard(props: Props): JSX.Element { const { modules, openEditModuleModal } = props + const enableHeaterShaker = useSelector( + featureFlagSelectors.getEnabledHeaterShaker + ) + const pipettesByMount = useSelector( stepFormSelectors.getPipettesForEditPipetteForm ) @@ -52,6 +56,12 @@ export function EditModulesCard(props: Props): JSX.Element { const showCrashInfoBox = warningsEnabled && (hasCrashableMagneticModule || hasCrashableTempModule) + const SUPPORTED_MODULE_TYPES_FILTERED = enableHeaterShaker + ? SUPPORTED_MODULE_TYPES + : SUPPORTED_MODULE_TYPES.filter( + moduleType => moduleType !== 'heaterShakerModuleType' + ) + return (
@@ -61,7 +71,7 @@ export function EditModulesCard(props: Props): JSX.Element { temperatureOnDeck={hasCrashableTempModule} /> )} - {SUPPORTED_MODULE_TYPES.map((moduleType, i) => { + {SUPPORTED_MODULE_TYPES_FILTERED.map((moduleType, i) => { const moduleData = modules[moduleType] if (moduleData) { return ( diff --git a/protocol-designer/src/components/modules/__tests__/EditModulesCard.test.tsx b/protocol-designer/src/components/modules/__tests__/EditModulesCard.test.tsx index d10de42cd67..1e7cd43ad49 100644 --- a/protocol-designer/src/components/modules/__tests__/EditModulesCard.test.tsx +++ b/protocol-designer/src/components/modules/__tests__/EditModulesCard.test.tsx @@ -29,6 +29,9 @@ const getDisableModuleRestrictionsMock = featureFlagSelectors.getDisableModuleRe const getPipettesForEditPipetteFormMock = stepFormSelectors.getPipettesForEditPipetteForm as jest.MockedFunction< typeof stepFormSelectors.getPipettesForEditPipetteForm > +const getEnabledHeaterShakerMock = featureFlagSelectors.getEnabledHeaterShaker as jest.MockedFunction< + typeof featureFlagSelectors.getEnabledHeaterShaker +> describe('EditModulesCard', () => { let store: any @@ -76,6 +79,7 @@ describe('EditModulesCard', () => { tiprackDefURI: null, }, }) + getEnabledHeaterShakerMock.mockReturnValue(true) props = { modules: {}, diff --git a/protocol-designer/src/localization/en/modules.json b/protocol-designer/src/localization/en/modules.json index 9d818ac206e..adf123ce862 100644 --- a/protocol-designer/src/localization/en/modules.json +++ b/protocol-designer/src/localization/en/modules.json @@ -2,19 +2,22 @@ "module_display_names": { "temperatureModuleType": "Temperature", "magneticModuleType": "Magnetic", - "thermocyclerModuleType": "Thermocycler" + "thermocyclerModuleType": "Thermocycler", + "heaterShakerModuleType": "Heater Shaker" }, "module_long_names": { "temperatureModuleType": "Temperature module", "magneticModuleType": "Magnetic module", - "thermocyclerModuleType": "Thermocycler module" + "thermocyclerModuleType": "Thermocycler module", + "heaterShakerModuleType": "Heater Shaker module" }, "model_display_name": { "temperatureModuleV1": "GEN1", "temperatureModuleV2": "GEN2", "magneticModuleV1": "GEN1", "magneticModuleV2": "GEN2", - "thermocyclerModuleV1": "GEN1" + "thermocyclerModuleV1": "GEN1", + "heaterShakerModuleV1": "GEN1" }, "status": { "engaged": "engaged",