Skip to content

fix(app): Add special casing for Stacker combination fixtures in deck conflict resolution#18988

Merged
smb2268 merged 15 commits intoedgefrom
app_deck-conflict-resolution
Jul 22, 2025
Merged

fix(app): Add special casing for Stacker combination fixtures in deck conflict resolution#18988
smb2268 merged 15 commits intoedgefrom
app_deck-conflict-resolution

Conversation

@smb2268
Copy link
Copy Markdown
Contributor

@smb2268 smb2268 commented Jul 21, 2025

closes RQA-4382, RQA-4319, EXEC-1416

Overview

This PR rounds out the necessary work for the modules & fixtures deck conflict resolution section of protocol setup on desktop and ODD. See changelog for specifics!

Test Plan and Hands on Testing

On both Desktop and ODD, check the following:

  1. For a protocol that needs a waste chute + stacker combination:
  • This displays correctly as a combination requirement
  • You can resolve this when you don't have a stacker or waste chute configured in deck config
  • You can resolve this when you have a waste chute OR stacker configured and need to add the other
  1. For a protocol that needs a stacker + mag block combination:
  • These display correctly as individual items in the list
  • You can resolve these in either order without messing up the other
  1. For a protocol that needs a mag block + staging area combination
  • These display correctly as individual items in the list
  • You can resolve these in either order without messing up the other
  1. Verify that waste chutes and mag blocks are visible when combined with stacker fixtures

Changelog

  1. Add new fixture images for waste chute + stacker and single slots and wire these up for display on desktop
  2. Update render ordering in BaseDeck so that waste chutes and mag blocks that are combo fixtures with stacker appear on the deck map - currently they are hidden
  3. Add ODDFixtureOption which is a copy of fixture option but with ODD only buttons that can’t be used in components, other fixture option is also used in Protocol Designer. Refactor relevant modals to use this component which closes EXEC-1634
  4. Refactor fixture section of setup to show Mag Block combination fixtures as individual items. Extract some logic in the ODD and Desktop versions of this FixtureTable and SetupFixtureList to utilities so we aren't duplicating this logic.
  5. Update SetupModulesList and ModuleTable to display Flex Stacker + waste chute combination correctly
  6. Create patchDeckConfigForRequiredFixture that enables us to add a fixture without removing a combination fixture. For example, if you have a waste chute in D3 and need to add a Flex Stacker, this will be patched into a waste chute flex stacker combination fixture
  7. Use NotConfiguredModal instead of AddFixtureModal for case in which you configure a non-conflicted fixture on ODD, which allows us to remove provided fixture logic in AddFixtureModal and remove deck config page of protocol setup, which seems to be a legacy page that was only shown under a modal in this specific instance. Move NotConfiguredModal to a shared location to enable this

Review requests

Take a look at the logic, especially Tamar :)

Risk assessment

Medium, but its been thoroughly tested

@smb2268 smb2268 self-assigned this Jul 21, 2025
@smb2268 smb2268 requested review from a team as code owners July 21, 2025 21:14
@codecov
Copy link
Copy Markdown

codecov bot commented Jul 21, 2025

Codecov Report

Attention: Patch coverage is 3.41530% with 707 lines in your changes missing coverage. Please review.

Project coverage is 23.43%. Comparing base (8278911) to head (a62c2f8).
Report is 247 commits behind head on edge.

Files with missing lines Patch % Lines
...Modal/getFilteredDeckConfigFixtureCompatibility.ts 0.00% 104 Missing ⚠️
components/src/hardware-sim/BaseDeck/BaseDeck.tsx 0.00% 67 Missing ⚠️
...nisms/LocationConflictModal/NotConfiguredModal.tsx 0.00% 64 Missing ⚠️
...ConflictModal/patchDeckConfigForRequiredFixture.ts 0.00% 60 Missing ⚠️
...rotocolRun/SetupModuleAndDeck/SetupModulesList.tsx 0.00% 54 Missing ⚠️
...Setup/ProtocolSetupModulesAndDeck/FixtureTable.tsx 0.00% 49 Missing ⚠️
...rotocolRun/SetupModuleAndDeck/SetupFixtureList.tsx 0.00% 48 Missing ⚠️
...DeviceDetailsDeckConfiguration/AddFixtureModal.tsx 0.00% 47 Missing ⚠️
...lSetup/ProtocolSetupModulesAndDeck/ModuleTable.tsx 0.00% 37 Missing ⚠️
app/src/molecules/ODDFixtureOption/index.tsx 0.00% 35 Missing ⚠️
... and 9 more
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             edge   #18988      +/-   ##
==========================================
- Coverage   23.85%   23.43%   -0.42%     
==========================================
  Files        3354     3364      +10     
  Lines      294222   294902     +680     
  Branches    30992    31027      +35     
==========================================
- Hits        70173    69106    -1067     
- Misses     224029   225771    +1742     
- Partials       20       25       +5     
Flag Coverage Δ
protocol-designer 18.97% <3.41%> (-0.12%) ⬇️
step-generation 5.32% <1.09%> (+0.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
.../organisms/DeviceDetailsDeckConfiguration/utils.ts 0.00% <ø> (ø)
app/src/organisms/ODD/ProtocolSetup/index.ts 0.00% <ø> (ø)
app/src/organisms/ODD/ProtocolSetup/types.ts 100.00% <ø> (ø)
app/src/pages/ODD/ProtocolSetup/index.tsx 0.00% <ø> (ø)
components/src/organisms/FixtureOption/index.tsx 95.12% <100.00%> (+30.41%) ⬆️
...organisms/HardwareConfigurator/AddFixtureModal.tsx 70.88% <ø> (-0.07%) ⬇️
shared-data/js/constants.ts 100.00% <100.00%> (ø)
...p/Devices/ProtocolRun/SetupModuleAndDeck/index.tsx 0.00% <0.00%> (ø)
...etupModulesAndDeck/ProtocolSetupModulesAndDeck.tsx 0.00% <0.00%> (ø)
...sources/runs/useModuleRenderInfoForProtocolById.ts 0.00% <0.00%> (ø)
... and 16 more

... and 234 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread components/src/organisms/FixtureOption/index.tsx Outdated
Comment thread app/src/molecules/ODDFixtureOption/index.tsx Outdated
Comment thread app/src/molecules/ODDFixtureOption/index.tsx Outdated
@smb2268 smb2268 requested review from a team and SyntaxColoring July 21, 2025 21:47
Comment thread app/src/molecules/ODDFixtureOption/index.tsx Outdated
Comment on lines +412 to +415
const slotName =
getModuleType(module.model) === FLEX_STACKER_MODULE_TYPE
? getStackerLocationFromSlotName(module.location.slotName)
: module.location.slotName
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this right?

StackedItemsOnDeck is defined as:

export interface StackedItemsOnDeck {
  [slotName: string]: StackItem[]
}

Which reads to me like it should always be like "A1": [stackItem1, stackItem2, ...], where "A1" is guaranteed to be a slot ID in the deck definition.

But this looks like it will return something like "STACKER A": [stackItem1, stackItem2, ...], so I'd expect downstream things to have trouble when they try to look up "STACKER A" as a slot ID in the deck def.

Copy link
Copy Markdown
Contributor Author

@smb2268 smb2268 Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it is correct based on the implementation for this util which is created around UI concerns for protocol setup. Items represented under "STACKER A": [stackItem1, stackItem2, ...] are in the hopper location, which doesn't exist in the deck definition. Items under "A4" will be stacked on the shuttle location. All users of this util expect this format, but I can update the comment to document this!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any way this can be made more type-safe? Like this, roughly, instead of a string?

type LocationForStackedItemsOnDeck =
  | {
    type: "stackerHopper",
    row: string // "A" - "D"
  } | {
    type: "offDeck"
  } | {
    type: "addressableAreaName"
    aaName: string
  }

You couldn't use these as keys for an object, but you could use them as keys for a Map. Or, instead of returning an object, you could return an array of objects where each object has a location property.

If that's not within reach, then at a minimum, yeah, some doc comments authoritatively describing the full format would be helpful. And I guess slotName in the interface should be called something other than slotName.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's a good suggestion, I think the refactor is out of scope for this PR but I can make a follow up ticket to address this and for now rename the variable and add some better documentation!

Comment thread app/src/assets/images/single_right_slot.png
compatibleCutoutFixtureIds,
deckDef,
robotName,
fakeCutoutFixtureId,
Copy link
Copy Markdown
Contributor

@TamarZanzouri TamarZanzouri Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you explain what is this prop for? is it always a fake cutout fixture? can we combine it with the cutoutFixtureId?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This prop is used in cases where we need to split a mag block combination fixture into two items, and represents the cutoutFixtureId that we should use for the display image and name in the UI. For example, if we have a flexStackerModuleV1WithMagneticBlockV1 fixture, we only want to render this as a magneticBlockV1 fixture on the UI but we want to maintain that the protocol requires the combination fixture in order to run, so we can't override the other props. I added this extra prop fakeCutoutFixtureId that in this case will equal magneticBlockV1 and if it exists, will be used for render purposes. Maybe a name like requiredCutoutFixtureIdForDisplay would make more sense here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to partialRequiredCutoutFixtureId - let me know what you think!

// if the module is a flex stacker in D4, check if it needs a waste chute
// combo fixture
if (
moduleDef.moduleType === FLEX_STACKER_MODULE_TYPE &&
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we check that the cutoutId === WASTE_CHUTE_CUTOUT instead of slot name?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have access to the cutoutId here, we just have the slot name that the module is loaded into!

Comment on lines +133 to +146
const deckConfigCompatabilityD3 = deckConfigCompatibility?.find(
configItem => configItem.cutoutId === 'cutoutD3'
)
if (
deckConfigCompatabilityD3 != null &&
WASTE_CHUTE_FLEX_STACKER_FIXTURES.includes(
deckConfigCompatabilityD3?.compatibleCutoutFixtureIds[0]
)
) {
const comboFixtureId =
deckConfigCompatabilityD3?.compatibleCutoutFixtureIds[0]
const comboFixtureConflict = !deckConfigCompatabilityD3?.compatibleCutoutFixtureIds.includes(
deckConfigCompatabilityD3.cutoutFixtureId
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can use replaceCutoutFixtureWithComboFixture if its helpful?

addressableAreaId: AddressableAreaNamesWithFakes,
deckDefinition: DeckDefinition
): CutoutConfigMap[][] => {
if (providedFixtureOptions != null) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why was this removed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what we were discussing yesterday - I was able to replace the usage of AddFixtureModal with NotConfiguredModal on ODD deck hardware setup. This was the only place that the providedFixtureOptions prop was used so I removed all of the logic related to it.

...existingCutoutConfig,
cutoutFixtureId: replacementCutoutFixtureId,
opentronsModuleSerialNumber: moduleSerialNumber,
if (
Copy link
Copy Markdown
Contributor

@TamarZanzouri TamarZanzouri Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice! you can also use the combo fixture method but you will need the aa

export const isFixtureCompatible = (
cutoutFixtureId: CutoutFixtureId,
compatibleCutoutFixtureIds: CutoutFixtureId[],
fakeCutoutFixtureId?: CutoutFixtureId
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im confused about this prop name can you elaborate?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same field as I described above -- here, cutoutFixtureId is the current CutoutFixtureId in the existing deck config, compatibleCutoutFixtureIds is an array that includes all possible CutoutFixtureId that can satisfy the protocol's hardware requirements, and fakeCutoutFixtureId if it exists will be the fixture id that for the current item assessing compatibility for.

For example, if a protocol requires a mag block with staging area combination fixture we'd need to determine compatibility for each the mag block and the staging area and show a status for them in hardware setup. If you had a staging area configured but no mag block, you'd see a connected status for the staging area item and a not configured status for the mag block.

The inputs/outputs of the function in this case would be as follows:
For Mag Block -
isFixtureCompatible('stagingAreaRightSlot', ['stagingAreaSlotWithMagneticBlockV1'], 'magneticBlockV1') will evaluate to false
For Staging Area -
isFixtureCompatible('stagingAreaRightSlot', ['stagingAreaSlotWithMagneticBlockV1'], 'stagingAreaRightSlot') will evaluate to true

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some clarifying comments to the functions here too

Copy link
Copy Markdown
Contributor

@TamarZanzouri TamarZanzouri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall looks great! had a few questions and suggestions

@smb2268 smb2268 requested a review from TamarZanzouri July 22, 2025 19:44
@smb2268 smb2268 force-pushed the app_deck-conflict-resolution branch from 68ecd5a to bdb0899 Compare July 22, 2025 19:48
Copy link
Copy Markdown
Contributor

@TamarZanzouri TamarZanzouri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks great! thank you for addressing the comments!

@smb2268 smb2268 merged commit 9317ea9 into edge Jul 22, 2025
54 of 55 checks passed
@smb2268 smb2268 deleted the app_deck-conflict-resolution branch July 22, 2025 21:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants