Skip to content

Commit

Permalink
feat(app): ODD protocol detail and protocol setup status for deck con…
Browse files Browse the repository at this point in the history
…figuration (#13855)

closes RAUT-667

* feat(app): update protocol detail and protocol setup status for deck configuration

ODD: Render the correct modules and deck status taking into account location conflicts, missing
hardware, and missing calibrations. Render this status in ProtocolSetup (Modules & deck) pages. Extend hooks to look for conflicting or missing hardware
  • Loading branch information
ncdiehl11 committed Nov 7, 2023
1 parent 52dc84a commit 8fedef6
Show file tree
Hide file tree
Showing 9 changed files with 457 additions and 215 deletions.
8 changes: 5 additions & 3 deletions app/src/assets/localization/en/device_details.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,13 @@
"magdeck_gen1_height": "Height: {{height}}",
"magdeck_gen2_height": "Height: {{height}} mm",
"max_engage_height": "Max Engage Height",
"missing_both": "missing hardware",
"missing_hardware": "missing hardware",
"missing_module_plural": "missing {{count}} modules",
"missing_module": "missing {{num}} module",
"missing_pipette": "missing {{num}} pipette",
"missing_pipettes_plural": "missing {{count}} pipettes",
"missing_instrument": "missing {{num}} instrument",
"missing_instruments_plural": "missing {{count}} instruments",
"missing_fixture": "missing {{num}} fixture",
"missing_fixtures_plural": "missing {{count}} fixtures",
"module_actions_unavailable": "Module actions unavailable while protocol is running",
"module_calibration_required_no_pipette_attached": "Module calibration required. Attach a pipette before running module calibration.",
"module_calibration_required_update_pipette_FW": "Update pipette firmware before proceeding with required module calibration.",
Expand Down
14 changes: 12 additions & 2 deletions app/src/assets/localization/en/protocol_setup.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,19 @@
"feedback_form_link": "Let us know!",
"fixture_name": "fixture",
"fixture": "Fixture",
"fixtures_connected_plural": "{{count}} fixtures attached",
"fixtures_connected": "{{count}} fixture attached",
"get_labware_offset_data": "Get Labware Offset Data",
"hardware_missing": "Missing hardware",
"heater_shaker_extra_attention": "Use latch controls for easy placement of labware.",
"heater_shaker_labware_list_view": "To add labware, use the toggle to control the latch",
"how_offset_data_works": "How labware offsets work",
"initial_liquids_num_plural": "{{count}} initial liquids",
"initial_liquids_num": "{{count}} initial liquid",
"initial_location": "Initial Location",
"install_modules_and_fixtures": "Install the required modules and power them on. Install the required fixtures and review the deck configuration.",
"instrument_calibrations_missing_plural": "Missing {{count}} calibrations",
"instrument_calibrations_missing": "Missing {{count}} calibration",
"instruments_connected_plural": "{{count}} instruments attached",
"instruments_connected": "{{count}} instrument attached",
"instruments": "Instruments",
Expand Down Expand Up @@ -114,6 +119,10 @@
"magnetic_module_extra_attention": "Opentrons recommends securing labware with the module’s bracket",
"map_view": "Map View",
"missing": "Missing",
"missing_gripper": "Missing gripper",
"missing_instruments": "Missing {{count}}",
"missing_pipettes_plural": "Missing {{count}} pipettes",
"missing_pipettes": "Missing {{count}} pipette",
"modal_instructions_title": "{{moduleName}} Setup Instructions",
"modal_instructions": "For step-by-step instructions on setting up your module, consult the Quickstart Guide that came in its box. You can also click the link below or scan the QR code to visit the modules section of the Opentrons Help Center.",
"module_and_deck_setup": "Modules & deck",
Expand All @@ -135,11 +144,12 @@
"modules": "Modules",
"mount_title": "{{mount}} MOUNT:",
"mount": "{{mount}} mount",
"multiple_fixtures_missing": "Missing {{count}} fixtures",
"multiple_modules_example": "Your protocol has two Temperature Modules. The Temperature Module attached to the first port starting from the left will be related to the first Temperature Module in your protocol while the second Temperature Module loaded would be related to the Temperature Module connected to the next port to the right. If using a hub, follow the same logic with the port ordering.",
"multiple_modules_explanation": "To use more than one of the same module in a protocol, you first need to plug in the module that’s called first in your protocol to the lowest numbered USB port on the robot. Continue in the same manner with additional modules.",
"multiple_modules_help_link_title": "See How To Set Up Modules of the Same Type",
"multiple_modules_learn_more": "Learn more about using multiple modules of the same type",
"multiple_modules_missing": "Multiple modules missing",
"multiple_modules_missing_plural": "Missing {{count}} modules",
"multiple_modules_modal": "Setting up multiple modules of the same type",
"multiple_modules": "Multiple modules of the same type",
"multiple_of_most_modules": "You can use multiples of most module types within a single Python protocol by connecting and loading the modules in a specific order. The robot will initialize the matching module attached to the lowest numbered port first, regardless of what deck slot it occupies.",
Expand All @@ -149,7 +159,7 @@
"no_labware_offset_data": "no labware offset data yet",
"no_modules_or_fixtures": "No modules or fixtures are specified for this protocol.",
"no_modules_specified": "no modules are specified for this protocol.",
"no_modules_used_in_this_protocol": "No modules used in this protocol",
"no_modules_used_in_this_protocol": "No hardware used in this protocol",
"no_tiprack_loaded": "Protocol must load a tip rack",
"no_tiprack_used": "Protocol must pick up a tip",
"no_usb_connection_required": "No USB connection required",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,11 @@ export function useHardwareStatusText(
})
}
} else if (countMissingPipettes > 0 && countMissingModules === 0) {
if (countMissingPipettes === 1) {
chipText = t('missing_pipette', {
num: countMissingPipettes,
})
} else {
chipText = t('missing_pipettes_plural', {
count: countMissingPipettes,
})
}
chipText = t('protocol_setup:missing_pipettes', {
count: countMissingPipettes,
})
} else if (countMissingPipettes > 0 && countMissingModules > 0) {
chipText = t('missing_both')
chipText = t('missing_hardware')
}
return i18n.format(chipText, 'capitalize')
}
22 changes: 22 additions & 0 deletions app/src/organisms/ProtocolSetupInstruments/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,25 @@ export function getAreInstrumentsReady(

return allSpeccedPipettesReady && isExtensionMountReady
}

export function getIncompleteInstrumentCount(
analysis: CompletedProtocolAnalysis,
attachedInstruments: Instruments
): number {
const speccedPipettes = analysis?.pipettes ?? []

const incompleteInstrumentCount = speccedPipettes.filter(loadedPipette => {
const attachedPipetteMatch = getPipetteMatch(
loadedPipette,
attachedInstruments
)
return attachedPipetteMatch?.data.calibratedOffset?.last_modified == null
}).length

const isExtensionMountReady = getProtocolUsesGripper(analysis)
? getAttachedGripper(attachedInstruments)?.data.calibratedOffset
?.last_modified != null
: true

return incompleteInstrumentCount + (isExtensionMountReady ? 0 : 1)
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,13 @@ describe('Hardware', () => {
hardwareType: 'fixture',
fixtureName: WASTE_CHUTE_LOAD_NAME,
location: { cutout: WASTE_CHUTE_SLOT },
hasSlotConflict: false,
},
{
hardwareType: 'fixture',
fixtureName: STAGING_AREA_LOAD_NAME,
location: { cutout: 'B3' },
hasSlotConflict: false,
},
],
isLoading: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ import {
useRunQuery,
useProtocolQuery,
useDoorQuery,
useModulesQuery,
useDeckConfigurationQuery,
} from '@opentrons/react-api-client'
import { renderWithProviders } from '@opentrons/components'
import { mockHeaterShaker } from '../../../../redux/modules/__fixtures__'
import {
FLEX_ROBOT_TYPE,
getDeckDefFromRobotType,
STAGING_AREA_LOAD_NAME,
} from '@opentrons/shared-data'
import ot3StandardDeckDef from '@opentrons/shared-data/deck/definitions/3/ot3_standard.json'

Expand Down Expand Up @@ -45,7 +49,12 @@ import { useIsHeaterShakerInProtocol } from '../../../../organisms/ModuleCard/ho
import { ConfirmAttachedModal } from '../ConfirmAttachedModal'
import { ProtocolSetup } from '..'

import type { CompletedProtocolAnalysis } from '@opentrons/shared-data'
import type { UseQueryResult } from 'react-query'
import type {
DeckConfiguration,
CompletedProtocolAnalysis,
Fixture,
} from '@opentrons/shared-data'

// Mock IntersectionObserver
class IntersectionObserver {
Expand Down Expand Up @@ -141,6 +150,12 @@ const mockConfirmAttachedModal = ConfirmAttachedModal as jest.MockedFunction<
const mockUseDoorQuery = useDoorQuery as jest.MockedFunction<
typeof useDoorQuery
>
const mockUseModulesQuery = useModulesQuery as jest.MockedFunction<
typeof useModulesQuery
>
const mockUseDeckConfigurationQuery = useDeckConfigurationQuery as jest.MockedFunction<
typeof useDeckConfigurationQuery
>
const mockUseToaster = useToaster as jest.MockedFunction<typeof useToaster>
const mockUseModuleCalibrationStatus = useModuleCalibrationStatus as jest.MockedFunction<
typeof useModuleCalibrationStatus
Expand Down Expand Up @@ -214,6 +229,12 @@ const mockDoorStatus = {
doorRequiredClosedForProtocol: true,
},
}
const mockFixture = {
fixtureId: 'mockId',
fixtureLocation: 'D1',
loadName: STAGING_AREA_LOAD_NAME,
} as Fixture

const MOCK_MAKE_SNACKBAR = jest.fn()

describe('ProtocolSetup', () => {
Expand Down Expand Up @@ -300,6 +321,12 @@ describe('ProtocolSetup', () => {
<div>mock ConfirmAttachedModal</div>
)
mockUseDoorQuery.mockReturnValue({ data: mockDoorStatus } as any)
mockUseModulesQuery.mockReturnValue({
data: { data: [mockHeaterShaker] },
} as any)
mockUseDeckConfigurationQuery.mockReturnValue({
data: [mockFixture],
} as UseQueryResult<DeckConfiguration>)
when(mockUseToaster)
.calledWith()
.mockReturnValue(({
Expand Down
Loading

0 comments on commit 8fedef6

Please sign in to comment.