Skip to content

Commit

Permalink
feat(protocol-designer): deleting staging area also deletes 4th colum… (
Browse files Browse the repository at this point in the history
#16701)

…n labware

closes RQA-3522 RQA-3467
  • Loading branch information
jerader authored Nov 6, 2024
1 parent b41e825 commit f66ffb9
Show file tree
Hide file tree
Showing 13 changed files with 329 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
"show_default_tips": "Show default tips",
"show_tips": "Show incompatible tips",
"slots_limit_reached": "Slots limit reached",
"staging_area_has_labware": "This staging area slot has labware",
"staging_area_will_delete_labware": "The staging area slot that you are about to delete has labware placed on it. If you make these changes to your protocol starting deck, the labware will be deleted as well.",
"stagingArea": "Staging area",
"swap_pipettes": "Swap pipettes",
"tell_us": "Tell us about your protocol",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { fireEvent, screen } from '@testing-library/react'
import { describe, it, beforeEach, vi, expect } from 'vitest'
import { renderWithProviders } from '../../../__testing-utils__'
import { i18n } from '../../../assets/localization'
import { ConfirmDeleteStagingAreaModal } from '..'
import type { ComponentProps } from 'react'

const render = (
props: ComponentProps<typeof ConfirmDeleteStagingAreaModal>
) => {
return renderWithProviders(<ConfirmDeleteStagingAreaModal {...props} />, {
i18nInstance: i18n,
})[0]
}

describe('ConfirmDeleteStagingAreaModal', () => {
let props: ComponentProps<typeof ConfirmDeleteStagingAreaModal>

beforeEach(() => {
props = {
onClose: vi.fn(),
onConfirm: vi.fn(),
}
})
it('renders the text and buttons work as expected', () => {
render(props)
screen.getByText('This staging area slot has labware')
screen.getByText(
'The staging area slot that you are about to delete has labware placed on it. If you make these changes to your protocol starting deck, the labware will be deleted as well.'
)
fireEvent.click(screen.getByText('Cancel'))
expect(props.onClose).toHaveBeenCalled()
fireEvent.click(screen.getByText('Continue'))
expect(props.onConfirm).toHaveBeenCalled()
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useTranslation } from 'react-i18next'
import { createPortal } from 'react-dom'
import {
Flex,
JUSTIFY_END,
Modal,
PrimaryButton,
SecondaryButton,
SPACING,
StyledText,
} from '@opentrons/components'
import { getTopPortalEl } from '../../components/portals/TopPortal'
import { HandleEnter } from '../../atoms/HandleEnter'

interface ConfirmDeleteStagingAreaModalProps {
onClose: () => void
onConfirm: () => void
}
export function ConfirmDeleteStagingAreaModal(
props: ConfirmDeleteStagingAreaModalProps
): JSX.Element {
const { onClose, onConfirm } = props
const { t, i18n } = useTranslation(['create_new_protocol', 'shared'])

return createPortal(
<HandleEnter onEnter={onConfirm}>
<Modal
zIndexOverlay={11}
title={t('staging_area_has_labware')}
type="info"
onClose={onClose}
footer={
<Flex
justifyContent={JUSTIFY_END}
gridGap={SPACING.spacing8}
padding={SPACING.spacing24}
>
<SecondaryButton
onClick={() => {
onClose()
}}
>
{t('shared:cancel')}
</SecondaryButton>
<PrimaryButton onClick={onConfirm}>
{i18n.format(t('shared:continue'), 'capitalize')}
</PrimaryButton>
</Flex>
}
>
<StyledText desktopStyle="bodyDefaultRegular">
{t('staging_area_will_delete_labware')}
</StyledText>
</Modal>
</HandleEnter>,
getTopPortalEl()
)
}
1 change: 1 addition & 0 deletions protocol-designer/src/organisms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './Alerts'
export * from './AnnouncementModal'
export * from './AssignLiquidsModal'
export * from './BlockingHintModal'
export * from './ConfirmDeleteStagingAreaModal'
export * from './DefineLiquidsModal'
export * from './EditInstrumentsModal'
export * from './EditNickNameModal'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const OT2_STANDARD_DECK_VIEW_LAYER_BLOCK_LIST: string[] = [
'fixedTrash',
]
export const lightFill = COLORS.grey35
const darkFill = COLORS.grey60
export const darkFill = COLORS.grey60

export function DeckSetupContainer(props: DeckSetupTabType): JSX.Element {
const { tab } = props
Expand Down
43 changes: 26 additions & 17 deletions protocol-designer/src/pages/Designer/DeckSetup/DeckSetupDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react'
import values from 'lodash/values'
import { Fragment, useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'

import { Module } from '@opentrons/components'
Expand Down Expand Up @@ -28,7 +28,9 @@ import { DeckItemHover } from './DeckItemHover'
import { SlotOverflowMenu } from './SlotOverflowMenu'
import { HoveredItems } from './HoveredItems'
import { SelectedHoveredItems } from './SelectedHoveredItems'
import { getAdjacentLabware } from './utils'

import type { ComponentProps, Dispatch, SetStateAction } from 'react'
import type { ModuleTemporalProperties } from '@opentrons/step-generation'
import type {
AddressableArea,
Expand All @@ -55,7 +57,7 @@ interface DeckSetupDetailsProps extends DeckSetupTabType {
hoveredFixture: Fixture | null
hoveredLabware: string | null
hoveredModule: ModuleModel | null
setHover: React.Dispatch<React.SetStateAction<string | null>>
setHover: Dispatch<SetStateAction<string | null>>
showGen1MultichannelCollisionWarnings: boolean
stagingAreaCutoutIds: CutoutId[]
selectedZoomInSlot?: DeckSlotId
Expand Down Expand Up @@ -83,9 +85,7 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
)
const selectedSlotInfo = useSelector(selectors.getZoomedInSlotInfo)
const { selectedSlot } = selectedSlotInfo
const [menuListId, setShowMenuListForId] = React.useState<DeckSlotId | null>(
null
)
const [menuListId, setShowMenuListForId] = useState<DeckSlotId | null>(null)
const dispatch = useDispatch<any>()

const {
Expand All @@ -100,7 +100,7 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
deckDef,
})
// initiate the slot's info
React.useEffect(() => {
useEffect(() => {
dispatch(
editSlotInfo({
createdNestedLabwareForSlot,
Expand Down Expand Up @@ -132,6 +132,15 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
? getSlotsWithCollisions(deckDef, allModules)
: []

const adjacentLabware =
preSelectedFixture != null && selectedSlot.cutout != null
? getAdjacentLabware(
preSelectedFixture,
selectedSlot.cutout,
activeDeckSetup.labware
)
: null

return (
<>
{/* all modules */}
Expand All @@ -146,7 +155,7 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
const moduleDef = getModuleDef2(moduleOnDeck.model)
const getModuleInnerProps = (
moduleState: ModuleTemporalProperties['moduleState']
): React.ComponentProps<typeof Module>['innerProps'] => {
): ComponentProps<typeof Module>['innerProps'] => {
if (moduleState.type === THERMOCYCLER_MODULE_TYPE) {
let lidMotorState = 'unknown'
if (tab === 'startingDeck' || moduleState.lidOpen) {
Expand Down Expand Up @@ -186,7 +195,7 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
zDimension: labwareLoadedOnModule?.def.dimensions.zDimension ?? 0,
}
return moduleOnDeck.slot !== selectedSlot.slot ? (
<React.Fragment key={moduleOnDeck.id}>
<Fragment key={moduleOnDeck.id}>
<Module
key={moduleOnDeck.id}
x={slotPosition[0]}
Expand Down Expand Up @@ -238,7 +247,7 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
/>
) : null}
</Module>
</React.Fragment>
</Fragment>
) : null
})}

Expand Down Expand Up @@ -276,7 +285,7 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
})
.map(addressableArea => {
return (
<React.Fragment key={addressableArea.id}>
<Fragment key={addressableArea.id}>
<DeckItemHover
isSelected={selectedZoomInSlot != null}
hover={hover}
Expand All @@ -291,18 +300,18 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
itemId={addressableArea.id}
tab={tab}
/>
</React.Fragment>
</Fragment>
)
})}
{/* all labware on deck NOT those in modules */}
{allLabware.map(labware => {
if (
labware.slot === 'offDeck' ||
allModules.some(m => m.id === labware.slot) ||
allLabware.some(lab => lab.id === labware.slot)
allLabware.some(lab => lab.id === labware.slot) ||
labware.id === adjacentLabware?.id
)
return null

const slotPosition = getPositionFromSlotId(labware.slot, deckDef)
const slotBoundingBox = getAddressableAreaFromSlotId(
labware.slot,
Expand All @@ -313,7 +322,7 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
return null
}
return labware.slot !== selectedSlot.slot ? (
<React.Fragment key={labware.id}>
<Fragment key={labware.id}>
<LabwareOnDeck
x={slotPosition[0]}
y={slotPosition[1]}
Expand All @@ -331,7 +340,7 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
itemId={labware.slot}
tab={tab}
/>
</React.Fragment>
</Fragment>
) : null
})}

Expand Down Expand Up @@ -376,7 +385,7 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
? slotForOnTheDeck
: allModules.find(module => module.id === slotForOnTheDeck)?.slot
return (
<React.Fragment key={labware.id}>
<Fragment key={labware.id}>
<LabwareOnDeck
x={slotPosition[0]}
y={slotPosition[1]}
Expand All @@ -394,7 +403,7 @@ export function DeckSetupDetails(props: DeckSetupDetailsProps): JSX.Element {
itemId={slotOnDeck ?? ''}
tab={tab}
/>
</React.Fragment>
</Fragment>
)
})}

Expand Down
44 changes: 42 additions & 2 deletions protocol-designer/src/pages/Designer/DeckSetup/DeckSetupTools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
MAGNETIC_MODULE_TYPE,
MAGNETIC_MODULE_V1,
MAGNETIC_MODULE_V2,
MODULE_MODELS,
OT2_ROBOT_TYPE,
} from '@opentrons/shared-data'

Expand All @@ -46,6 +47,7 @@ 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 { FIXTURES, MOAM_MODELS } from './constants'
import { getSlotInformation } from '../utils'
import { getModuleModelsBySlot, getDeckErrors } from './utils'
Expand All @@ -71,6 +73,9 @@ export function DeckSetupTools(props: DeckSetupToolsProps): JSX.Element | null {
const { makeSnackbar } = useKitchen()
const selectedSlotInfo = useSelector(selectors.getZoomedInSlotInfo)
const robotType = useSelector(getRobotType)
const [showDeleteLabwareModal, setShowDeleteLabwareModal] = useState<
ModuleModel | 'clear' | null
>(null)
const isDismissedModuleHint = useSelector(getDismissedHints).includes(
'change_magnet_module_model'
)
Expand Down Expand Up @@ -154,6 +159,7 @@ export function DeckSetupTools(props: DeckSetupToolsProps): JSX.Element | null {
createdModuleForSlot,
createdLabwareForSlot,
createFixtureForSlots,
matchingLabwareFor4thColumn,
} = getSlotInformation({ deckSetup, slot })

let fixtures: Fixture[] = []
Expand Down Expand Up @@ -218,6 +224,10 @@ export function DeckSetupTools(props: DeckSetupToolsProps): JSX.Element | null {
if (createdNestedLabwareForSlot != null) {
dispatch(deleteContainer({ labwareId: createdNestedLabwareForSlot.id }))
}
// clear labware on staging area 4th column slot
if (matchingLabwareFor4thColumn != null) {
dispatch(deleteContainer({ labwareId: matchingLabwareFor4thColumn.id }))
}
}
handleResetToolbox()
setSelectedHardware(null)
Expand Down Expand Up @@ -278,6 +288,26 @@ export function DeckSetupTools(props: DeckSetupToolsProps): JSX.Element | null {
}
return (
<>
{showDeleteLabwareModal != null ? (
<ConfirmDeleteStagingAreaModal
onClose={() => {
setShowDeleteLabwareModal(null)
}}
onConfirm={() => {
if (showDeleteLabwareModal === 'clear') {
handleClear()
handleResetToolbox()
} else if (MODULE_MODELS.includes(showDeleteLabwareModal)) {
setSelectedHardware(showDeleteLabwareModal)
dispatch(selectFixture({ fixture: null }))
dispatch(selectModule({ moduleModel: showDeleteLabwareModal }))
dispatch(selectLabware({ labwareDefUri: null }))
dispatch(selectNestedLabware({ nestedLabwareDefUri: null }))
}
setShowDeleteLabwareModal(null)
}}
/>
) : null}
{changeModuleWarning}
<Toolbox
height="calc(100vh - 64px)"
Expand All @@ -302,8 +332,12 @@ export function DeckSetupTools(props: DeckSetupToolsProps): JSX.Element | null {
</StyledText>
}
onCloseClick={() => {
handleClear()
handleResetToolbox()
if (matchingLabwareFor4thColumn != null) {
setShowDeleteLabwareModal('clear')
} else {
handleClear()
handleResetToolbox()
}
}}
onConfirmClick={() => {
handleConfirm()
Expand Down Expand Up @@ -407,6 +441,12 @@ export function DeckSetupTools(props: DeckSetupToolsProps): JSX.Element | null {
!isDismissedModuleHint
) {
displayModuleWarning(true)
} else if (
selectedFixture === 'stagingArea' ||
(selectedFixture === 'wasteChuteAndStagingArea' &&
matchingLabwareFor4thColumn != null)
) {
setShowDeleteLabwareModal(model)
} else {
setSelectedHardware(model)
dispatch(selectFixture({ fixture: null }))
Expand Down
Loading

0 comments on commit f66ffb9

Please sign in to comment.