Skip to content

Commit bb1f1ce

Browse files
authored
refactor(app): make on device display deck configurator updates live (#15202)
Instead of the previous method of staging a batch of changes to deck configuration before commiting them with the confirm button, to avoid synchronization issues, make the ODD deck configurator behave like the desktop version. all updates immediately permeate to the robot's source of truth Re: https://opentrons.atlassian.net/browse/RQA-2669
1 parent 76cbb52 commit bb1f1ce

File tree

11 files changed

+202
-299
lines changed

11 files changed

+202
-299
lines changed

app/src/assets/localization/en/shared.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"robot_is_busy": "Robot is busy",
6464
"robot_is_reachable_but_not_responding": "This robot's API server is not responding correctly to requests at IP address {{hostname}}",
6565
"robot_was_seen_but_is_unreachable": "This robot has been seen recently, but is currently not reachable at IP address {{hostname}}",
66+
"save": "save",
6667
"something_went_wrong": "something went wrong",
6768
"sort_by": "Sort by",
6869
"stand_back_robot_is_in_motion": "Stand back, robot is in motion",

app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ const Template: Story<React.ComponentProps<typeof AddFixtureModal>> = args => (
2626
export const Default = Template.bind({})
2727
Default.args = {
2828
fixtureLocation: 'cutoutD3',
29-
setShowAddFixtureModal: () => {},
29+
closeModal: () => {},
3030
isOnDevice: true,
3131
}

app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.tsx

Lines changed: 8 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,13 @@ import type {
5454
CutoutConfig,
5555
CutoutId,
5656
CutoutFixtureId,
57-
DeckConfiguration,
5857
} from '@opentrons/shared-data'
5958
import type { ModalHeaderBaseProps } from '../../molecules/Modal/types'
6059
import type { LegacyModalProps } from '../../molecules/LegacyModal'
6160

6261
interface AddFixtureModalProps {
6362
cutoutId: CutoutId
64-
setShowAddFixtureModal: (showAddFixtureModal: boolean) => void
65-
setCurrentDeckConfig?: React.Dispatch<React.SetStateAction<CutoutConfig[]>>
63+
closeModal: () => void
6664
providedFixtureOptions?: CutoutFixtureId[]
6765
isOnDevice?: boolean
6866
}
@@ -75,8 +73,7 @@ type OptionStage =
7573

7674
export function AddFixtureModal({
7775
cutoutId,
78-
setShowAddFixtureModal,
79-
setCurrentDeckConfig,
76+
closeModal,
8077
providedFixtureOptions,
8178
isOnDevice = false,
8279
}: AddFixtureModalProps): JSX.Element {
@@ -109,18 +106,14 @@ export function AddFixtureModal({
109106
slotName: getCutoutDisplayName(cutoutId),
110107
}),
111108
hasExitIcon: providedFixtureOptions == null,
112-
onClick: () => {
113-
setShowAddFixtureModal(false)
114-
},
109+
onClick: closeModal,
115110
}
116111

117112
const modalProps: LegacyModalProps = {
118113
title: t('add_to_slot', {
119114
slotName: getCutoutDisplayName(cutoutId),
120115
}),
121-
onClose: () => {
122-
setShowAddFixtureModal(false)
123-
},
116+
onClose: closeModal,
124117
closeOnOutsideClick: true,
125118
childrenPadding: SPACING.spacing24,
126119
width: '26.75rem',
@@ -289,22 +282,7 @@ export function AddFixtureModal({
289282
)
290283
}
291284

292-
const handleAddODD = (addedCutoutConfigs: CutoutConfig[]): void => {
293-
if (setCurrentDeckConfig != null)
294-
setCurrentDeckConfig(
295-
(prevDeckConfig: DeckConfiguration): DeckConfiguration =>
296-
prevDeckConfig.map((fixture: CutoutConfig) => {
297-
const replacementCutoutConfig = addedCutoutConfigs.find(
298-
c => c.cutoutId === fixture.cutoutId
299-
)
300-
return replacementCutoutConfig ?? fixture
301-
})
302-
)
303-
304-
setShowAddFixtureModal(false)
305-
}
306-
307-
const handleAddDesktop = (addedCutoutConfigs: CutoutConfig[]): void => {
285+
const handleAddFixture = (addedCutoutConfigs: CutoutConfig[]): void => {
308286
const newDeckConfig = deckConfig.map(fixture => {
309287
const replacementCutoutConfig = addedCutoutConfigs.find(
310288
c => c.cutoutId === fixture.cutoutId
@@ -313,7 +291,7 @@ export function AddFixtureModal({
313291
})
314292

315293
updateDeckConfiguration(newDeckConfig)
316-
setShowAddFixtureModal(false)
294+
closeModal()
317295
}
318296

319297
const fixtureOptions = availableOptions.map(cutoutConfigs => (
@@ -327,9 +305,7 @@ export function AddFixtureModal({
327305
)}
328306
buttonText={t('add')}
329307
onClickHandler={() => {
330-
isOnDevice
331-
? handleAddODD(cutoutConfigs)
332-
: handleAddDesktop(cutoutConfigs)
308+
handleAddFixture(cutoutConfigs)
333309
}}
334310
isOnDevice={isOnDevice}
335311
/>
@@ -341,9 +317,7 @@ export function AddFixtureModal({
341317
<Modal
342318
header={modalHeader}
343319
onOutsideClick={() =>
344-
providedFixtureOptions != null
345-
? null
346-
: setShowAddFixtureModal(false)
320+
providedFixtureOptions != null ? null : closeModal()
347321
}
348322
>
349323
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing32}>

app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/AddFixtureModal.test.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@ import type { Modules } from '@opentrons/api-client'
2323
vi.mock('@opentrons/react-api-client')
2424
vi.mock('../../../resources/deck_configuration')
2525

26-
const mockSetShowAddFixtureModal = vi.fn()
26+
const mockCloseModal = vi.fn()
2727
const mockUpdateDeckConfiguration = vi.fn()
28-
const mockSetCurrentDeckConfig = vi.fn()
2928

3029
const render = (props: React.ComponentProps<typeof AddFixtureModal>) => {
3130
return renderWithProviders(<AddFixtureModal {...props} />, {
@@ -39,8 +38,7 @@ describe('Touchscreen AddFixtureModal', () => {
3938
beforeEach(() => {
4039
props = {
4140
cutoutId: 'cutoutD3',
42-
setShowAddFixtureModal: mockSetShowAddFixtureModal,
43-
setCurrentDeckConfig: mockSetCurrentDeckConfig,
41+
closeModal: mockCloseModal,
4442
isOnDevice: true,
4543
}
4644
vi.mocked(useUpdateDeckConfigurationMutation).mockReturnValue({
@@ -69,7 +67,6 @@ describe('Touchscreen AddFixtureModal', () => {
6967
render(props)
7068
fireEvent.click(screen.getAllByText('Select options')[1])
7169
fireEvent.click(screen.getAllByText('Add')[0])
72-
expect(mockSetCurrentDeckConfig).toHaveBeenCalled()
7370
})
7471

7572
it('when fixture options are provided, should only render those options', () => {
@@ -96,7 +93,7 @@ describe('Desktop AddFixtureModal', () => {
9693
beforeEach(() => {
9794
props = {
9895
cutoutId: 'cutoutD3',
99-
setShowAddFixtureModal: mockSetShowAddFixtureModal,
96+
closeModal: mockCloseModal,
10097
}
10198
vi.mocked(useUpdateDeckConfigurationMutation).mockReturnValue({
10299
updateDeckConfiguration: mockUpdateDeckConfiguration,

app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeviceDetailsDeckConfiguration.test.tsx

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import { fireEvent, screen } from '@testing-library/react'
33
import { when } from 'vitest-when'
44
import { describe, it, beforeEach, vi, afterEach } from 'vitest'
55

6+
import {
7+
DeckConfiguration,
8+
TRASH_BIN_ADAPTER_FIXTURE,
9+
} from '@opentrons/shared-data'
610
import { DeckConfigurator } from '@opentrons/components'
711
import {
812
useModulesQuery,
@@ -16,8 +20,12 @@ import { DeckFixtureSetupInstructionsModal } from '../DeckFixtureSetupInstructio
1620
import { useIsEstopNotDisengaged } from '../../../resources/devices/hooks/useIsEstopNotDisengaged'
1721
import { DeviceDetailsDeckConfiguration } from '../'
1822
import { useNotifyCurrentMaintenanceRun } from '../../../resources/maintenance_runs'
19-
import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration'
23+
import {
24+
useDeckConfigurationEditingTools,
25+
useNotifyDeckConfigurationQuery,
26+
} from '../../../resources/deck_configuration'
2027

28+
import type { UseQueryResult } from 'react-query'
2129
import type { MaintenanceRun } from '@opentrons/api-client'
2230
import type * as OpentronsComponents from '@opentrons/components'
2331

@@ -35,6 +43,12 @@ vi.mock('../../../resources/maintenance_runs')
3543
vi.mock('../../../resources/devices/hooks/useIsEstopNotDisengaged')
3644
vi.mock('../../../resources/deck_configuration')
3745

46+
const mockDeckConfig = [
47+
{
48+
cutoutId: 'cutoutC3',
49+
cutoutFixtureId: TRASH_BIN_ADAPTER_FIXTURE,
50+
},
51+
]
3852
const ROBOT_NAME = 'otie'
3953
const mockUpdateDeckConfiguration = vi.fn()
4054
const RUN_STATUSES = {
@@ -63,9 +77,6 @@ describe('DeviceDetailsDeckConfiguration', () => {
6377
robotName: ROBOT_NAME,
6478
}
6579
vi.mocked(useModulesQuery).mockReturnValue({ data: { data: [] } } as any)
66-
vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({
67-
data: [],
68-
} as any)
6980
vi.mocked(useUpdateDeckConfigurationMutation).mockReturnValue({
7081
updateDeckConfiguration: mockUpdateDeckConfiguration,
7182
} as any)
@@ -83,6 +94,14 @@ describe('DeviceDetailsDeckConfiguration', () => {
8394
.calledWith(ROBOT_NAME)
8495
.thenReturn(false)
8596
when(vi.mocked(useIsRobotViewable)).calledWith(ROBOT_NAME).thenReturn(true)
97+
vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({
98+
data: mockDeckConfig,
99+
} as UseQueryResult<DeckConfiguration>)
100+
vi.mocked(useDeckConfigurationEditingTools).mockReturnValue({
101+
addFixtureToCutout: vi.fn(),
102+
removeFixtureFromCutout: vi.fn(),
103+
addFixtureModal: null,
104+
})
86105
})
87106

88107
afterEach(() => {
@@ -132,7 +151,9 @@ describe('DeviceDetailsDeckConfiguration', () => {
132151
})
133152

134153
it('should render no deck fixtures, if deck configs are not set', () => {
135-
vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue([] as any)
154+
vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({
155+
data: [],
156+
} as any)
136157
render(props)
137158
screen.getByText('No deck fixtures')
138159
})

app/src/organisms/DeviceDetailsDeckConfiguration/index.tsx

Lines changed: 14 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -19,32 +19,26 @@ import {
1919
StyledText,
2020
TYPOGRAPHY,
2121
} from '@opentrons/components'
22-
import {
23-
useModulesQuery,
24-
useUpdateDeckConfigurationMutation,
25-
} from '@opentrons/react-api-client'
22+
import { useModulesQuery } from '@opentrons/react-api-client'
2623
import {
2724
getCutoutDisplayName,
2825
getFixtureDisplayName,
29-
SINGLE_RIGHT_CUTOUTS,
3026
SINGLE_SLOT_FIXTURES,
31-
SINGLE_LEFT_SLOT_FIXTURE,
32-
SINGLE_RIGHT_SLOT_FIXTURE,
33-
SINGLE_CENTER_SLOT_FIXTURE,
34-
SINGLE_LEFT_CUTOUTS,
3527
getDeckDefFromRobotType,
3628
FLEX_ROBOT_TYPE,
3729
} from '@opentrons/shared-data'
3830

3931
import { useNotifyCurrentMaintenanceRun } from '../../resources/maintenance_runs'
4032
import { Banner } from '../../atoms/Banner'
4133
import { DeckFixtureSetupInstructionsModal } from './DeckFixtureSetupInstructionsModal'
42-
import { AddFixtureModal } from './AddFixtureModal'
4334
import { useIsRobotViewable, useRunStatuses } from '../Devices/hooks'
4435
import { useIsEstopNotDisengaged } from '../../resources/devices/hooks/useIsEstopNotDisengaged'
45-
import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration'
36+
import {
37+
useDeckConfigurationEditingTools,
38+
useNotifyDeckConfigurationQuery,
39+
} from '../../resources/deck_configuration'
4640

47-
import type { CutoutFixtureId, CutoutId } from '@opentrons/shared-data'
41+
import type { CutoutId } from '@opentrons/shared-data'
4842

4943
const DECK_CONFIG_REFETCH_INTERVAL = 5000
5044
const RUN_REFETCH_INTERVAL = 5000
@@ -65,20 +59,13 @@ export function DeviceDetailsDeckConfiguration({
6559
showSetupInstructionsModal,
6660
setShowSetupInstructionsModal,
6761
] = React.useState<boolean>(false)
68-
const [showAddFixtureModal, setShowAddFixtureModal] = React.useState<boolean>(
69-
false
70-
)
71-
const [targetCutoutId, setTargetCutoutId] = React.useState<CutoutId | null>(
72-
null
73-
)
7462

7563
const { data: modulesData } = useModulesQuery()
7664
const deckConfig =
7765
useNotifyDeckConfigurationQuery({
7866
refetchInterval: DECK_CONFIG_REFETCH_INTERVAL,
7967
}).data ?? []
8068
const deckDef = getDeckDefFromRobotType(FLEX_ROBOT_TYPE)
81-
const { updateDeckConfiguration } = useUpdateDeckConfigurationMutation()
8269
const { isRunRunning } = useRunStatuses()
8370
const { data: maintenanceRunData } = useNotifyCurrentMaintenanceRun({
8471
refetchInterval: RUN_REFETCH_INTERVAL,
@@ -87,59 +74,11 @@ export function DeviceDetailsDeckConfiguration({
8774
const isMaintenanceRunExisting = maintenanceRunData?.data?.id != null
8875
const isRobotViewable = useIsRobotViewable(robotName)
8976

90-
const handleClickAdd = (cutoutId: CutoutId): void => {
91-
setTargetCutoutId(cutoutId)
92-
setShowAddFixtureModal(true)
93-
}
94-
95-
const handleClickRemove = (
96-
cutoutId: CutoutId,
97-
cutoutFixtureId: CutoutFixtureId
98-
): void => {
99-
let replacementFixtureId: CutoutFixtureId = SINGLE_CENTER_SLOT_FIXTURE
100-
if (SINGLE_RIGHT_CUTOUTS.includes(cutoutId)) {
101-
replacementFixtureId = SINGLE_RIGHT_SLOT_FIXTURE
102-
} else if (SINGLE_LEFT_CUTOUTS.includes(cutoutId)) {
103-
replacementFixtureId = SINGLE_LEFT_SLOT_FIXTURE
104-
}
105-
106-
const fixtureGroup =
107-
deckDef.cutoutFixtures.find(cf => cf.id === cutoutFixtureId)
108-
?.fixtureGroup ?? {}
109-
110-
let newDeckConfig = deckConfig
111-
if (cutoutId in fixtureGroup) {
112-
const groupMap =
113-
fixtureGroup[cutoutId]?.find(group =>
114-
Object.entries(group).every(([cId, cfId]) =>
115-
deckConfig.find(
116-
config =>
117-
config.cutoutId === cId && config.cutoutFixtureId === cfId
118-
)
119-
)
120-
) ?? {}
121-
newDeckConfig = deckConfig.map(cutoutConfig =>
122-
cutoutConfig.cutoutId in groupMap
123-
? {
124-
...cutoutConfig,
125-
cutoutFixtureId: replacementFixtureId,
126-
opentronsModuleSerialNumber: undefined,
127-
}
128-
: cutoutConfig
129-
)
130-
} else {
131-
newDeckConfig = deckConfig.map(cutoutConfig =>
132-
cutoutConfig.cutoutId === cutoutId
133-
? {
134-
...cutoutConfig,
135-
cutoutFixtureId: replacementFixtureId,
136-
opentronsModuleSerialNumber: undefined,
137-
}
138-
: cutoutConfig
139-
)
140-
}
141-
updateDeckConfiguration(newDeckConfig)
142-
}
77+
const {
78+
addFixtureToCutout,
79+
removeFixtureFromCutout,
80+
addFixtureModal,
81+
} = useDeckConfigurationEditingTools(false)
14382

14483
// do not show standard slot in fixture display list
14584
const { displayList: fixtureDisplayList } = deckConfig.reduce<{
@@ -199,12 +138,7 @@ export function DeviceDetailsDeckConfiguration({
199138

200139
return (
201140
<>
202-
{showAddFixtureModal && targetCutoutId != null ? (
203-
<AddFixtureModal
204-
cutoutId={targetCutoutId}
205-
setShowAddFixtureModal={setShowAddFixtureModal}
206-
/>
207-
) : null}
141+
{addFixtureModal}
208142
{showSetupInstructionsModal ? (
209143
<DeckFixtureSetupInstructionsModal
210144
setShowSetupInstructionsModal={setShowSetupInstructionsModal}
@@ -278,8 +212,8 @@ export function DeviceDetailsDeckConfiguration({
278212
: deckConfig.map(({ cutoutId }) => cutoutId)
279213
}
280214
deckConfig={deckConfig}
281-
handleClickAdd={handleClickAdd}
282-
handleClickRemove={handleClickRemove}
215+
handleClickAdd={addFixtureToCutout}
216+
handleClickRemove={removeFixtureFromCutout}
283217
/>
284218
</Flex>
285219
<Flex

0 commit comments

Comments
 (0)