From ebdfc849421639ab07bc342aaf78ed689b668b14 Mon Sep 17 00:00:00 2001 From: Jethary Alcid <66035149+jerader@users.noreply.github.com> Date: Fri, 20 Dec 2024 11:14:50 -0500 Subject: [PATCH] fix(protocol-designer): editing labware on top of a module and adapter fixes (#17161) address RQA-3777 RQA-3804 --- .../localization/en/protocol_steps.json | 3 +- .../Designer/DeckSetup/DeckSetupTools.tsx | 56 ++++++++++++++++--- .../__tests__/DeckSetupTools.test.tsx | 4 +- .../Designer/ProtocolSteps/StepSummary.tsx | 22 ++++---- .../src/step-forms/actions/thunks.ts | 55 +++++++++++++++++- 5 files changed, 118 insertions(+), 22 deletions(-) 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 48b0ef87bba..d9c2a84a95b 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupTools.tsx +++ b/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupTools.tsx @@ -33,7 +33,8 @@ import { createDeckFixture, deleteDeckFixture, } from '../../../step-forms/actions/additionalItems' -import { createModule, deleteModule } from '../../../step-forms/actions' +import { deleteModule } from '../../../step-forms/actions' +import { getSavedStepForms } from '../../../step-forms/selectors' import { getDeckSetupForActiveItem } from '../../../top-selectors/labware-locations' import { createContainer, @@ -50,9 +51,12 @@ 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 { ConfirmDeleteStagingAreaModal } from '../../../organisms' +import { + createContainerAboveModule, + createModuleEntityAndChangeForm, +} from '../../../step-forms/actions/thunks' import { BUTTON_LINK_STYLE } from '../../../atoms' +import { ConfirmDeleteStagingAreaModal } from '../../../organisms' import { getSlotInformation } from '../utils' import { ALL_ORDERED_CATEGORIES, FIXTURES, MOAM_MODELS } from './constants' import { LabwareTools } from './LabwareTools' @@ -63,6 +67,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 @@ -80,6 +91,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) @@ -255,7 +267,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 })) } @@ -309,11 +325,33 @@ export function DeckSetupTools(props: DeckSetupToolsProps): JSX.Element | null { } if (selectedModuleModel != null) { // create module + const moduleType = getModuleType(selectedModuleModel) + + 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: getModuleType(selectedModuleModel), + type: moduleType, model: selectedModuleModel, + moduleSteps, + pauseSteps, }) ) } @@ -344,7 +382,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 5eab480710e..1c3a7c6a472 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/__tests__/DeckSetupTools.test.tsx +++ b/protocol-designer/src/pages/Designer/DeckSetup/__tests__/DeckSetupTools.test.tsx @@ -10,6 +10,7 @@ import { i18n } from '../../../../assets/localization' import { renderWithProviders } from '../../../../__testing-utils__' import { deleteContainer } from '../../../../labware-ingred/actions' import { deleteModule } from '../../../../step-forms/actions' +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' @@ -19,7 +20,6 @@ import { getDeckSetupForActiveItem } from '../../../../top-selectors/labware-loc import { DeckSetupTools } from '../DeckSetupTools' import { LabwareTools } from '../LabwareTools' -import type * as React from 'react' import type { LabwareDefinition2 } from '@opentrons/shared-data' vi.mock('../LabwareTools') @@ -31,6 +31,7 @@ vi.mock('../../../../step-forms/actions') vi.mock('../../../../step-forms/actions/additionalItems') vi.mock('../../../../labware-ingred/selectors') vi.mock('../../../../tutorial/selectors') +vi.mock('../../../../step-forms/selectors') const render = (props: React.ComponentProps) => { return renderWithProviders(, { i18nInstance: i18n, @@ -65,6 +66,7 @@ describe('DeckSetupTools', () => { additionalEquipmentOnDeck: {}, pipettes: {}, }) + vi.mocked(getSavedStepForms).mockReturnValue({}) vi.mocked(getDismissedHints).mockReturnValue([]) }) afterEach(() => { diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/StepSummary.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/StepSummary.tsx index f97d73e469f..19a648e11b0 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/StepSummary.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/StepSummary.tsx @@ -79,6 +79,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 @@ -124,9 +125,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, + }, + }) + ) + }) +}