diff --git a/protocol-designer/fixtures/protocol/8/newAdvancedSettingsAndMultiTemp.json b/protocol-designer/fixtures/protocol/8/newAdvancedSettingsAndMultiTemp.json index 9c6d24abaca..8b8d129418f 100644 --- a/protocol-designer/fixtures/protocol/8/newAdvancedSettingsAndMultiTemp.json +++ b/protocol-designer/fixtures/protocol/8/newAdvancedSettingsAndMultiTemp.json @@ -3624,7 +3624,7 @@ "key": "dd1b37d6-eb8c-4616-bc8a-e1f17556234a", "params": { "pipetteId": "21087f15-4c03-4587-8a2b-1ba0b5a501a0", - "volume": 1 + "volume": 10 } }, { diff --git a/protocol-designer/src/assets/localization/en/protocol_steps.json b/protocol-designer/src/assets/localization/en/protocol_steps.json index 2f498904c61..0ed3f34caf8 100644 --- a/protocol-designer/src/assets/localization/en/protocol_steps.json +++ b/protocol-designer/src/assets/localization/en/protocol_steps.json @@ -143,5 +143,6 @@ "volume_per_well": "Volume per well", "well_name": "Well {{wellName}}", "well_order_title": "{{prefix}} well order", - "well_position": "Well position: X {{x}} Y {{y}} Z {{z}} (mm)" + "well_position": "Well position: X {{x}} Y {{y}} Z {{z}} (mm)", + "unknown_module": "Unknown module" } diff --git a/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupTools.tsx b/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupTools.tsx index f5b1fe252cd..61b4c73de2f 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupTools.tsx +++ b/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupTools.tsx @@ -36,8 +36,9 @@ import { createDeckFixture, deleteDeckFixture, } from '../../../step-forms/actions/additionalItems' -import { createModule, deleteModule } from '../../../step-forms/actions' import { getAdditionalEquipment } from '../../../step-forms/selectors' +import { deleteModule } from '../../../step-forms/actions' +import { getSavedStepForms } from '../../../step-forms/selectors' import { getDeckSetupForActiveItem } from '../../../top-selectors/labware-locations' import { createContainer, @@ -54,8 +55,11 @@ import { useBlockingHint } from '../../../organisms/BlockingHintModal/useBlockin import { selectors } from '../../../labware-ingred/selectors' import { useKitchen } from '../../../organisms/Kitchen/hooks' import { getDismissedHints } from '../../../tutorial/selectors' -import { createContainerAboveModule } from '../../../step-forms/actions/thunks' import { LINK_BUTTON_STYLE, NAV_BAR_HEIGHT_REM } from '../../../atoms' +import { + createContainerAboveModule, + createModuleEntityAndChangeForm, +} from '../../../step-forms/actions/thunks' import { ConfirmDeleteStagingAreaModal } from '../../../organisms' import { getSlotInformation } from '../utils' import { ALL_ORDERED_CATEGORIES, FIXTURES, MOAM_MODELS } from './constants' @@ -67,6 +71,13 @@ import type { AddressableAreaName, ModuleModel } from '@opentrons/shared-data' import type { ThunkDispatch } from '../../../types' import type { Fixture } from './constants' +const mapModTypeToStepType: Record = { + heaterShakerModuleType: 'heaterShaker', + magneticModuleType: 'magnet', + temperatureModuleType: 'temperature', + thermocyclerModuleType: 'thermocycler', +} + interface DeckSetupToolsProps { onCloseClick: () => void setHoveredLabware: (defUri: string | null) => void @@ -91,6 +102,7 @@ export function DeckSetupTools(props: DeckSetupToolsProps): JSX.Element | null { const { makeSnackbar } = useKitchen() const selectedSlotInfo = useSelector(selectors.getZoomedInSlotInfo) const robotType = useSelector(getRobotType) + const savedSteps = useSelector(getSavedStepForms) const [showDeleteLabwareModal, setShowDeleteLabwareModal] = useState< ModuleModel | 'clear' | null >(null) @@ -272,7 +284,11 @@ export function DeckSetupTools(props: DeckSetupToolsProps): JSX.Element | null { if ( createdLabwareForSlot != null && (!keepExistingLabware || - createdLabwareForSlot.labwareDefURI !== selectedLabwareDefUri) + createdLabwareForSlot.labwareDefURI !== selectedLabwareDefUri || + // if nested labware changes but labware doesn't, still delete both + (createdLabwareForSlot.labwareDefURI === selectedLabwareDefUri && + createdNestedLabwareForSlot?.labwareDefURI !== + selectedNestedLabwareDefUri)) ) { dispatch(deleteContainer({ labwareId: createdLabwareForSlot.id })) } @@ -332,11 +348,32 @@ export function DeckSetupTools(props: DeckSetupToolsProps): JSX.Element | null { makeSnackbar(t('gripper_required_for_plate_reader') as string) return } + + const moduleSteps = Object.values(savedSteps).filter(step => { + return ( + step.stepType === mapModTypeToStepType[moduleType] && + // only update module steps that match the old moduleId + // to accommodate instances of MoaM + step.moduleId === createdModuleForSlot?.id + ) + }) + + const pauseSteps = Object.values(savedSteps).filter(step => { + return ( + step.stepType === 'pause' && + // only update pause steps that match the old moduleId + // to accommodate instances of MoaM + step.moduleId === createdModuleForSlot?.id + ) + }) + dispatch( - createModule({ + createModuleEntityAndChangeForm({ slot, type: moduleType, model: selectedModuleModel, + moduleSteps, + pauseSteps, }) ) } @@ -364,7 +401,11 @@ export function DeckSetupTools(props: DeckSetupToolsProps): JSX.Element | null { if ( selectedModuleModel != null && selectedLabwareDefUri != null && - createdLabwareForSlot?.labwareDefURI !== selectedLabwareDefUri + (createdLabwareForSlot?.labwareDefURI !== selectedLabwareDefUri || + // if nested labware changes but labware doesn't, still create both both + (createdLabwareForSlot.labwareDefURI === selectedLabwareDefUri && + createdNestedLabwareForSlot?.labwareDefURI !== + selectedNestedLabwareDefUri)) ) { // create adapter + labware on module dispatch( diff --git a/protocol-designer/src/pages/Designer/DeckSetup/__tests__/DeckSetupTools.test.tsx b/protocol-designer/src/pages/Designer/DeckSetup/__tests__/DeckSetupTools.test.tsx index 6d7cc88479e..c3866a806e6 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/__tests__/DeckSetupTools.test.tsx +++ b/protocol-designer/src/pages/Designer/DeckSetup/__tests__/DeckSetupTools.test.tsx @@ -13,6 +13,7 @@ import { deleteContainer } from '../../../../labware-ingred/actions' import { useKitchen } from '../../../../organisms/Kitchen/hooks' import { deleteModule } from '../../../../step-forms/actions' import { getAdditionalEquipment } from '../../../../step-forms/selectors' +import { getSavedStepForms } from '../../../../step-forms/selectors' import { getRobotType } from '../../../../file-data/selectors' import { getEnableAbsorbanceReader } from '../../../../feature-flags/selectors' import { deleteDeckFixture } from '../../../../step-forms/actions/additionalItems' @@ -72,6 +73,7 @@ describe('DeckSetupTools', () => { additionalEquipmentOnDeck: {}, pipettes: {}, }) + vi.mocked(getSavedStepForms).mockReturnValue({}) vi.mocked(getDismissedHints).mockReturnValue([]) vi.mocked(getAdditionalEquipment).mockReturnValue({}) vi.mocked(useKitchen).mockReturnValue({ diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/StepSummary.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/StepSummary.tsx index 047efee38cb..a7d4349bad6 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/StepSummary.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/StepSummary.tsx @@ -83,6 +83,7 @@ interface StepSummaryProps { export function StepSummary(props: StepSummaryProps): JSX.Element | null { const { currentStep, stepDetails } = props const { t } = useTranslation(['protocol_steps', 'application']) + const unknownModule = t('unkonwn_module') const labwareNicknamesById = useSelector(getLabwareNicknamesById) const additionalEquipmentEntities = useSelector( getAdditionalEquipmentEntities @@ -130,9 +131,8 @@ export function StepSummary(props: StepSummaryProps): JSX.Element | null { engageHeight, magnetAction, } = currentStep - const magneticModuleDisplayName = getModuleDisplayName( - modules[magneticModuleId].model - ) + const magneticModuleDisplayName = + getModuleDisplayName(modules[magneticModuleId]?.model) ?? unknownModule stepSummaryContent = magnetAction === 'engage' ? ( diff --git a/protocol-designer/src/step-forms/actions/thunks.ts b/protocol-designer/src/step-forms/actions/thunks.ts index 0a0f5f17227..65079b0fe2d 100644 --- a/protocol-designer/src/step-forms/actions/thunks.ts +++ b/protocol-designer/src/step-forms/actions/thunks.ts @@ -1,12 +1,21 @@ import { createContainer } from '../../labware-ingred/actions' import { getDeckSetupForActiveItem } from '../../top-selectors/labware-locations' +import { uuid } from '../../utils' +import { changeSavedStepForm } from '../../steplist/actions' -import type { DeckSlotId } from '@opentrons/shared-data' +import type { + DeckSlotId, + ModuleModel, + ModuleType, +} from '@opentrons/shared-data' import type { ThunkAction } from '../../types' import type { CreateContainerAction, RenameLabwareAction, } from '../../labware-ingred/actions' +import type { CreateModuleAction } from './modules' +import type { ChangeSavedStepFormAction } from '../../steplist/actions' +import type { FormData } from '../../form-types' export interface CreateContainerAboveModuleArgs { slot: DeckSlotId @@ -37,3 +46,47 @@ export const createContainerAboveModule: ( }) ) } + +interface ModuleAndChangeFormArgs { + slot: DeckSlotId + type: ModuleType + model: ModuleModel + moduleSteps: FormData[] + pauseSteps: FormData[] +} +export const createModuleEntityAndChangeForm: ( + args: ModuleAndChangeFormArgs +) => ThunkAction = args => ( + dispatch, + getState +) => { + const { slot, model, type, moduleSteps, pauseSteps } = args + const moduleId = `${uuid()}:${type}` + + dispatch({ + type: 'CREATE_MODULE', + payload: { slot, model, type, id: moduleId }, + }) + + // if steps are created with the module that has been regenerated, migrate them to use the correct moduleId + moduleSteps.forEach(step => { + dispatch( + changeSavedStepForm({ + stepId: step.id, + update: { + moduleId, + }, + }) + ) + }) + pauseSteps.forEach(step => { + dispatch( + changeSavedStepForm({ + stepId: step.id, + update: { + moduleId, + }, + }) + ) + }) +} diff --git a/protocol-designer/src/steplist/fieldLevel/errors.ts b/protocol-designer/src/steplist/fieldLevel/errors.ts index 7405f097643..892e7f0a11a 100644 --- a/protocol-designer/src/steplist/fieldLevel/errors.ts +++ b/protocol-designer/src/steplist/fieldLevel/errors.ts @@ -34,7 +34,7 @@ export type ErrorChecker = (value: unknown) => string | null export const requiredField: ErrorChecker = (value: unknown) => !value ? FIELD_ERRORS.REQUIRED : null export const isTimeFormat: ErrorChecker = (value: unknown): string | null => { - const timeRegex = new RegExp(/^\d{1,2}:\d{1,2}:\d{1,2}$/g) + const timeRegex = new RegExp(/^\d{1,2}:(?:[0-5]?\d):(?:[0-5]?\d)$/g) return (typeof value === 'string' && timeRegex.test(value)) || !value ? null : FIELD_ERRORS.BAD_TIME_HMS @@ -42,7 +42,7 @@ export const isTimeFormat: ErrorChecker = (value: unknown): string | null => { export const isTimeFormatMinutesSeconds: ErrorChecker = ( value: unknown ): string | null => { - const timeRegex = new RegExp(/^\d{1,2}:\d{1,2}$/g) + const timeRegex = new RegExp(/^\d+:[0-5]?\d$/g) return (typeof value === 'string' && timeRegex.test(value)) || !value ? null : FIELD_ERRORS.BAD_TIME_MS diff --git a/step-generation/src/commandCreators/compound/transfer.ts b/step-generation/src/commandCreators/compound/transfer.ts index e892dc94b3d..18f99b7e036 100644 --- a/step-generation/src/commandCreators/compound/transfer.ts +++ b/step-generation/src/commandCreators/compound/transfer.ts @@ -263,7 +263,7 @@ export const transfer: CommandCreator = ( ? [ curryCommandCreator(configureForVolume, { pipetteId: args.pipette, - volume: chunksPerSubTransfer, + volume: subTransferVol, }), ] : []