-
Notifications
You must be signed in to change notification settings - Fork 178
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(app): Fix nested labware UI in Error Recovery (#16634)
Closes RQA-3440 This PR fixes copy and deck map issues when addressing labware nested in other labware. 5eab60c - Split the util that gets the labware location and renders appropriate copy into two separate utils. c753bc5 - Fixes copy issues. We recently refactored slot location display utils, and I just forgot to use the correct, new prop here and update relevant i18n strings. caf9340 - Fixes deck map issues. Essentially, if there are multiple labware in the slot with the failed labware, only render the topmost labware. This is consistent with deck map behavior in other places in the app. Yes, it's a pain and annoying to have to do this outside of the deck map, but after discussion with other FE devs, it sounds like the deck map redesign will fundamentally change the way we render all objects on the deck, so I don't think it's worth the effort to improve deck map internals currently.
- Loading branch information
Showing
9 changed files
with
419 additions
and
226 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
202 changes: 55 additions & 147 deletions
202
app/src/local-resources/labware/utils/getLabwareDisplayLocation.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,180 +1,88 @@ | ||
import { | ||
getLabwareDefURI, | ||
getLabwareDisplayName, | ||
getModuleDisplayName, | ||
getModuleType, | ||
getOccludedSlotCountForModule, | ||
} from '@opentrons/shared-data' | ||
|
||
import { | ||
getModuleModel, | ||
getModuleDisplayLocation, | ||
} from '/app/local-resources/modules' | ||
import { getLabwareLocation } from './getLabwareLocation' | ||
|
||
import type { TFunction } from 'i18next' | ||
import type { | ||
LabwareDefinition2, | ||
LabwareLocation, | ||
RobotType, | ||
} from '@opentrons/shared-data' | ||
import type { LoadedLabwares } from '/app/local-resources/labware' | ||
import type { LoadedModules } from '/app/local-resources/modules' | ||
LocationSlotOnlyParams, | ||
LocationFullParams, | ||
} from './getLabwareLocation' | ||
|
||
interface LabwareDisplayLocationBaseParams { | ||
location: LabwareLocation | null | ||
loadedModules: LoadedModules | ||
loadedLabwares: LoadedLabwares | ||
robotType: RobotType | ||
export interface DisplayLocationSlotOnlyParams extends LocationSlotOnlyParams { | ||
t: TFunction | ||
isOnDevice?: boolean | ||
} | ||
|
||
export interface LabwareDisplayLocationSlotOnly | ||
extends LabwareDisplayLocationBaseParams { | ||
detailLevel: 'slot-only' | ||
} | ||
|
||
export interface LabwareDisplayLocationFull | ||
extends LabwareDisplayLocationBaseParams { | ||
detailLevel?: 'full' | ||
allRunDefs: LabwareDefinition2[] | ||
export interface DisplayLocationFullParams extends LocationFullParams { | ||
t: TFunction | ||
isOnDevice?: boolean | ||
} | ||
|
||
export type LabwareDisplayLocationParams = | ||
| LabwareDisplayLocationSlotOnly | ||
| LabwareDisplayLocationFull | ||
export type DisplayLocationParams = | ||
| DisplayLocationSlotOnlyParams | ||
| DisplayLocationFullParams | ||
|
||
// detailLevel applies to nested labware. If 'full', return copy that includes the actual peripheral that nests the | ||
// labware, ex, "in module XYZ in slot C1". | ||
// If 'slot-only', return only the slot name, ex "in slot C1". | ||
export function getLabwareDisplayLocation( | ||
params: LabwareDisplayLocationParams | ||
params: DisplayLocationParams | ||
): string { | ||
const { | ||
loadedLabwares, | ||
loadedModules, | ||
location, | ||
robotType, | ||
t, | ||
isOnDevice = false, | ||
detailLevel = 'full', | ||
} = params | ||
const { t, isOnDevice = false } = params | ||
const locationResult = getLabwareLocation(params) | ||
|
||
if (location == null) { | ||
console.error('Cannot get labware display location. No location provided.') | ||
if (locationResult == null) { | ||
return '' | ||
} else if (location === 'offDeck') { | ||
return t('off_deck') | ||
} else if ('slotName' in location) { | ||
return isOnDevice | ||
? location.slotName | ||
: t('slot', { slot_name: location.slotName }) | ||
} else if ('addressableAreaName' in location) { | ||
return isOnDevice | ||
? location.addressableAreaName | ||
: t('slot', { slot_name: location.addressableAreaName }) | ||
} else if ('moduleId' in location) { | ||
const moduleModel = getModuleModel(loadedModules, location.moduleId) | ||
if (moduleModel == null) { | ||
console.error('labware is located on an unknown module model') | ||
return '' | ||
} | ||
const slotName = getModuleDisplayLocation(loadedModules, location.moduleId) | ||
} | ||
|
||
if (detailLevel === 'slot-only') { | ||
return t('slot', { slot_name: slotName }) | ||
} | ||
const { slotName, moduleModel, adapterName } = locationResult | ||
|
||
return isOnDevice | ||
? `${getModuleDisplayName(moduleModel)}, ${slotName}` | ||
: t('module_in_slot', { | ||
count: getOccludedSlotCountForModule( | ||
getModuleType(moduleModel), | ||
robotType | ||
), | ||
module: getModuleDisplayName(moduleModel), | ||
slot_name: slotName, | ||
}) | ||
} else if ('labwareId' in location) { | ||
if (!Array.isArray(loadedLabwares)) { | ||
console.error('Cannot get display location from loaded labwares object') | ||
return '' | ||
if (slotName === 'offDeck') { | ||
return t('off_deck') | ||
} | ||
// Simple slot location | ||
else if (moduleModel == null && adapterName == null) { | ||
return isOnDevice ? slotName : t('slot', { slot_name: slotName }) | ||
} | ||
// Module location without adapter | ||
else if (moduleModel != null && adapterName == null) { | ||
if (params.detailLevel === 'slot-only') { | ||
return t('slot', { slot_name: slotName }) | ||
} else { | ||
return isOnDevice | ||
? `${getModuleDisplayName(moduleModel)}, ${slotName}` | ||
: t('module_in_slot', { | ||
count: getOccludedSlotCountForModule( | ||
getModuleType(moduleModel), | ||
params.robotType | ||
), | ||
module: getModuleDisplayName(moduleModel), | ||
slot_name: slotName, | ||
}) | ||
} | ||
const adapter = loadedLabwares.find(lw => lw.id === location.labwareId) | ||
|
||
if (adapter == null) { | ||
console.error('labware is located on an unknown adapter') | ||
return '' | ||
} else if (detailLevel === 'slot-only') { | ||
return getLabwareDisplayLocation({ | ||
...params, | ||
location: adapter.location, | ||
} | ||
// Adapter locations | ||
else if (adapterName != null) { | ||
if (moduleModel == null) { | ||
return t('adapter_in_slot', { | ||
adapter: adapterName, | ||
slot: slotName, | ||
}) | ||
} else if (detailLevel === 'full') { | ||
const { allRunDefs } = params as LabwareDisplayLocationFull | ||
const adapterDef = allRunDefs.find( | ||
def => getLabwareDefURI(def) === adapter?.definitionUri | ||
) | ||
const adapterDisplayName = | ||
adapterDef != null ? getLabwareDisplayName(adapterDef) : '' | ||
|
||
if (adapter.location === 'offDeck') { | ||
return t('off_deck') | ||
} else if ( | ||
'slotName' in adapter.location || | ||
'addressableAreaName' in adapter.location | ||
) { | ||
const slotName = | ||
'slotName' in adapter.location | ||
? adapter.location.slotName | ||
: adapter.location.addressableAreaName | ||
return t('adapter_in_slot', { | ||
adapter: adapterDisplayName, | ||
slot: slotName, | ||
}) | ||
} else if ('moduleId' in adapter.location) { | ||
const moduleIdUnderAdapter = adapter.location.moduleId | ||
|
||
if (!Array.isArray(loadedModules)) { | ||
console.error( | ||
'Cannot get display location from loaded modules object' | ||
) | ||
return '' | ||
} | ||
|
||
const moduleModel = loadedModules.find( | ||
module => module.id === moduleIdUnderAdapter | ||
)?.model | ||
if (moduleModel == null) { | ||
console.error('labware is located on an adapter on an unknown module') | ||
return '' | ||
} | ||
const slotName = getModuleDisplayLocation( | ||
loadedModules, | ||
adapter.location.moduleId | ||
) | ||
|
||
return t('adapter_in_mod_in_slot', { | ||
count: getOccludedSlotCountForModule( | ||
getModuleType(moduleModel), | ||
robotType | ||
), | ||
module: getModuleDisplayName(moduleModel), | ||
adapter: adapterDisplayName, | ||
slot: slotName, | ||
}) | ||
} else { | ||
console.error( | ||
'Unhandled adapter location for determining display location.' | ||
) | ||
return '' | ||
} | ||
} else { | ||
console.error('Unhandled detail level for determining display location.') | ||
return '' | ||
return t('adapter_in_mod_in_slot', { | ||
count: getOccludedSlotCountForModule( | ||
getModuleType(moduleModel), | ||
params.robotType | ||
), | ||
module: getModuleDisplayName(moduleModel), | ||
adapter: adapterName, | ||
slot: slotName, | ||
}) | ||
} | ||
} else { | ||
console.error('display location could not be established: ', location) | ||
return '' | ||
} | ||
} |
147 changes: 147 additions & 0 deletions
147
app/src/local-resources/labware/utils/getLabwareLocation.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import { getLabwareDefURI, getLabwareDisplayName } from '@opentrons/shared-data' | ||
|
||
import { | ||
getModuleDisplayLocation, | ||
getModuleModel, | ||
} from '/app/local-resources/modules' | ||
|
||
import type { | ||
LabwareDefinition2, | ||
LabwareLocation, | ||
ModuleModel, | ||
RobotType, | ||
} from '@opentrons/shared-data' | ||
import type { LoadedLabwares } from '/app/local-resources/labware' | ||
import type { LoadedModules } from '/app/local-resources/modules' | ||
|
||
export interface LocationResult { | ||
slotName: string | ||
moduleModel?: ModuleModel | ||
adapterName?: string | ||
} | ||
|
||
interface BaseParams { | ||
location: LabwareLocation | null | ||
loadedModules: LoadedModules | ||
loadedLabwares: LoadedLabwares | ||
robotType: RobotType | ||
} | ||
|
||
export interface LocationSlotOnlyParams extends BaseParams { | ||
detailLevel: 'slot-only' | ||
} | ||
|
||
export interface LocationFullParams extends BaseParams { | ||
allRunDefs: LabwareDefinition2[] | ||
detailLevel?: 'full' | ||
} | ||
|
||
export type GetLabwareLocationParams = | ||
| LocationSlotOnlyParams | ||
| LocationFullParams | ||
|
||
// detailLevel returns additional information about the module and adapter in the same location, if applicable. | ||
// if 'slot-only', returns the underlying slot location. | ||
export function getLabwareLocation( | ||
params: GetLabwareLocationParams | ||
): LocationResult | null { | ||
const { | ||
loadedLabwares, | ||
loadedModules, | ||
location, | ||
detailLevel = 'full', | ||
} = params | ||
|
||
if (location == null) { | ||
return null | ||
} else if (location === 'offDeck') { | ||
return { slotName: 'offDeck' } | ||
} else if ('slotName' in location) { | ||
return { slotName: location.slotName } | ||
} else if ('addressableAreaName' in location) { | ||
return { slotName: location.addressableAreaName } | ||
} else if ('moduleId' in location) { | ||
const moduleModel = getModuleModel(loadedModules, location.moduleId) | ||
if (moduleModel == null) { | ||
console.error('labware is located on an unknown module model') | ||
return null | ||
} | ||
const slotName = getModuleDisplayLocation(loadedModules, location.moduleId) | ||
|
||
return { | ||
slotName, | ||
moduleModel, | ||
} | ||
} else if ('labwareId' in location) { | ||
if (!Array.isArray(loadedLabwares)) { | ||
console.error('Cannot get location from loaded labwares object') | ||
return null | ||
} | ||
|
||
const adapter = loadedLabwares.find(lw => lw.id === location.labwareId) | ||
|
||
if (adapter == null) { | ||
console.error('labware is located on an unknown adapter') | ||
return null | ||
} else if (detailLevel === 'slot-only') { | ||
return getLabwareLocation({ | ||
...params, | ||
location: adapter.location, | ||
}) | ||
} else if (detailLevel === 'full') { | ||
const { allRunDefs } = params as LocationFullParams | ||
const adapterDef = allRunDefs.find( | ||
def => getLabwareDefURI(def) === adapter?.definitionUri | ||
) | ||
const adapterName = | ||
adapterDef != null ? getLabwareDisplayName(adapterDef) : '' | ||
|
||
if (adapter.location === 'offDeck') { | ||
return { slotName: 'offDeck', adapterName } | ||
} else if ( | ||
'slotName' in adapter.location || | ||
'addressableAreaName' in adapter.location | ||
) { | ||
const slotName = | ||
'slotName' in adapter.location | ||
? adapter.location.slotName | ||
: adapter.location.addressableAreaName | ||
return { slotName, adapterName } | ||
} else if ('moduleId' in adapter.location) { | ||
const moduleIdUnderAdapter = adapter.location.moduleId | ||
|
||
if (!Array.isArray(loadedModules)) { | ||
console.error('Cannot get location from loaded modules object') | ||
return null | ||
} | ||
|
||
const moduleModel = loadedModules.find( | ||
module => module.id === moduleIdUnderAdapter | ||
)?.model | ||
|
||
if (moduleModel == null) { | ||
console.error('labware is located on an adapter on an unknown module') | ||
return null | ||
} | ||
|
||
const slotName = getModuleDisplayLocation( | ||
loadedModules, | ||
adapter.location.moduleId | ||
) | ||
|
||
return { | ||
slotName, | ||
moduleModel, | ||
adapterName, | ||
} | ||
} else { | ||
return null | ||
} | ||
} else { | ||
console.error('Unhandled detailLevel.') | ||
return null | ||
} | ||
} else { | ||
return null | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.