Skip to content

Commit

Permalink
fix(app): rewire cli and jupyter snippets to offsets modals (#12180)
Browse files Browse the repository at this point in the history
Closes RLAB-300 by rewiring and fixing the cli and Jupyter labware offset snippets to their three
new locations in the app.

Closes RLAB-300
  • Loading branch information
b-cooper committed Mar 3, 2023
1 parent 089fa48 commit 6147fb9
Show file tree
Hide file tree
Showing 16 changed files with 210 additions and 438 deletions.
4 changes: 2 additions & 2 deletions app/src/assets/localization/en/app_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@
"usb_to_ethernet_adapter_toast_message": "An update is available for Realtek USB-to-Ethernet adapter driver",
"usb_to_ethernet_adapter_link": "go to Realtek.com",
"usb_to_ethernet_not_connected": "No USB-to-Ethernet adapter connected",
"show_link_labware_data": "Show Link to Get Labware Offset Data",
"show_link_labware_data_description": "If you need to access Labware Offset data outside of the Opentrons App, enabling this setting will display a link to get Offset Data in the Recent Runs overflow menu and in the Labware Setup section of the Protocol page.",
"show_labware_offset_snippets": "Show Labware Offset data code snippets",
"show_labware_offset_snippets_description": "Only for users who need to apply Labware Offset data outside of the Opentrons App. When enabled, code snippets for Jupyter Notebook and SSH are available during protocol setup.",
"allow_sending_all_protocols_to_ot3": "Allow Sending All Protocols to OT-3",
"allow_sending_all_protocols_to_ot3_description": "Enable the \"Send to OT-3\" menu item for each imported protocol, even if protocol analysis fails or does not recognize it as designed for the OT-3.",
"clear_unavail_robots": "Clear Unavailable Robots",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,25 @@ const labwareOffsets = [
vector: { x: 0, y: 0, z: 2.99999 },
},
]
const analysisCommands = protocolWithMagTempTC.commands.map(c => {
if (c.commandType === 'loadModule') {
return {
...c,
params: {
...c.params,
model: protocolWithMagTempTC.modules.find(
m => m.id === c.params.moduleId
)?.model as ModuleModel,
},
}
}
return c
})

const juptyerPrefix =
'import opentrons.execute\nprotocol = opentrons.execute.get_protocol_api("2.12")\n\n'
'import opentrons.execute\nprotocol = opentrons.execute.get_protocol_api("2.13")\n\n'
const cliPrefix =
'from opentrons import protocol_api\n\nmetadata = {\n "apiLevel": "2.12"\n}\n\ndef run(protocol: protocol_api.ProtocolContext):'
'from opentrons import protocol_api\n\nmetadata = {\n "apiLevel": "2.13"\n}\n\ndef run(protocol: protocol_api.ProtocolContext):'

describe('createSnippet', () => {
it('should generate expected python snippet for jupyter rounding vector values to 2 fixed decimal values', () => {
Expand All @@ -142,7 +156,9 @@ describe('createSnippet', () => {

const resultingSnippet = createSnippet(
'jupyter',
protocolWithMagTempTC,
analysisCommands,
protocolWithMagTempTC.labware,
protocolWithMagTempTC.modules,
labwareOffsets
)

Expand Down Expand Up @@ -191,7 +207,9 @@ describe('createSnippet', () => {
'labware_9 = protocol.load_labware("corning_24_wellplate_3.4ml_flat", location="6")\n labware_9.set_offset(x=0.00, y=0.00, z=3.00)'
const resultingSnippet = createSnippet(
'cli',
protocolWithMagTempTC,
analysisCommands,
protocolWithMagTempTC.labware,
protocolWithMagTempTC.modules,
labwareOffsets
)

Expand Down
176 changes: 86 additions & 90 deletions app/src/molecules/PythonLabwareOffsetSnippet/createSnippet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,112 +3,108 @@ import { getLoadedLabwareDefinitionsByUri } from '@opentrons/shared-data'
import { getLabwareDefinitionUri } from '../../organisms/Devices/ProtocolRun/utils/getLabwareDefinitionUri'
import { LabwareOffset } from '@opentrons/api-client'
import { getLabwareOffsetLocation } from '../../organisms/Devices/ProtocolRun/utils/getLabwareOffsetLocation'
import type { CompletedProtocolAnalysis } from '@opentrons/shared-data'
import type {
LoadedLabware,
LoadedModule,
RunTimeCommand,
} from '@opentrons/shared-data'

const PYTHON_INDENT = ' '
const JUPYTER_PREFIX =
'import opentrons.execute\nprotocol = opentrons.execute.get_protocol_api("2.12")\n\n'
const CLI_PREFIX = `from opentrons import protocol_api\n\nmetadata = {\n${PYTHON_INDENT}"apiLevel": "2.12"\n}\n\ndef run(protocol: protocol_api.ProtocolContext):`
'import opentrons.execute\nprotocol = opentrons.execute.get_protocol_api("2.13")\n\n'
const CLI_PREFIX = `from opentrons import protocol_api\n\nmetadata = {\n${PYTHON_INDENT}"apiLevel": "2.13"\n}\n\ndef run(protocol: protocol_api.ProtocolContext):`

export function createSnippet(
mode: 'jupyter' | 'cli',
protocol: CompletedProtocolAnalysis,
labwareOffsets?: LabwareOffset[]
commands: RunTimeCommand[],
labware: LoadedLabware[],
modules: LoadedModule[],
labwareOffsets?: Array<Omit<LabwareOffset, 'createdAt' | 'id'>>
): string | null {
let moduleVariableById: { [moduleId: string]: string } = {}
let labwareCount = 0
const loadCommandLines = protocol.commands.reduce<string[]>(
(acc, command) => {
let loadStatement = ''
let addendum = null
if (command.commandType === 'loadLabware') {
labwareCount = labwareCount + 1
if (command.result == null) return acc

const loadedLabware = protocol.labware.find(
item => item.id === command.result?.labwareId
)
if (loadedLabware == null) return acc
const labwareDefinitions = getLoadedLabwareDefinitionsByUri(
protocol.commands
)
const { loadName } = labwareDefinitions[
loadedLabware.definitionUri
].parameters
if (command.params.location === 'offDeck') {
loadStatement = `labware_${labwareCount} = protocol.load_labware("${String(
loadName
)}", location="offDeck")`
} else if ('slotName' in command.params.location) {
// load labware on deck
const { slotName } = command.params.location
loadStatement = `labware_${labwareCount} = protocol.load_labware("${String(
loadName
)}", location="${String(slotName)}")`
} else if ('moduleId' in command.params.location) {
// load labware on module
const moduleVariable =
moduleVariableById[command.params.location.moduleId]
loadStatement = `labware_${labwareCount} = ${moduleVariable}.load_labware("${String(
loadName
)}")`
}
const labwareDefUri = getLabwareDefinitionUri(
command.result.labwareId,
protocol.labware,
labwareDefinitions
)

const offsetLocation = getLabwareOffsetLocation(
command.result.labwareId,
protocol.commands,
protocol.modules
)
const loadCommandLines = commands.reduce<string[]>((acc, command) => {
let loadStatement = ''
let addendum = null
if (command.commandType === 'loadLabware') {
labwareCount = labwareCount + 1
const loadedLabware = labware.find(
item => item.id === command.result?.labwareId
)
if (loadedLabware == null) return acc
const labwareDefinitions = getLoadedLabwareDefinitionsByUri(commands)
const { loadName } = labwareDefinitions[
loadedLabware.definitionUri
].parameters
if (command.params.location === 'offDeck') {
loadStatement = `labware_${labwareCount} = protocol.load_labware("${String(
loadName
)}", location="offDeck")`
} else if ('slotName' in command.params.location) {
// load labware on deck
const { slotName } = command.params.location
loadStatement = `labware_${labwareCount} = protocol.load_labware("${String(
loadName
)}", location="${String(slotName)}")`
} else if ('moduleId' in command.params.location) {
// load labware on module
const moduleVariable =
moduleVariableById[command.params.location.moduleId]
loadStatement = `labware_${labwareCount} = ${moduleVariable}.load_labware("${String(
loadName
)}")`
}
const labwareDefUri = getLabwareDefinitionUri(
command.result?.labwareId ?? '',
labware,
labwareDefinitions
)
const offsetLocation = getLabwareOffsetLocation(
command.result?.labwareId ?? '',
commands,
modules
)

const labwareOffset = labwareOffsets?.find(offset => {
return (
offset.definitionUri === labwareDefUri &&
isEqual(offset.location, offsetLocation)
)
})
if (labwareOffset == null) {
addendum = [loadStatement, '']
} else {
const { x, y, z } = labwareOffset.vector
addendum = [
loadStatement,
`labware_${labwareCount}.set_offset(x=${String(
x.toFixed(2)
)}, y=${String(y.toFixed(2))}, z=${String(z.toFixed(2))})`,
'',
]
}
} else if (command.commandType === 'loadModule') {
// load module on deck
const moduleVariable = `module_${
Object.keys(moduleVariableById).length + 1
}`
moduleVariableById = {
...moduleVariableById,
[command.result?.moduleId ?? '']: moduleVariable,
}
const module = protocol.modules.find(
module => module.id === command.params.moduleId
const labwareOffset = labwareOffsets?.find(offset => {
return (
offset.definitionUri === labwareDefUri &&
isEqual(offset.location, offsetLocation)
)
const model = module?.model
const { slotName } = command.params.location
})
if (labwareOffset == null) {
addendum = [loadStatement, '']
} else {
const { x, y, z } = labwareOffset.vector
addendum = [
`${moduleVariable} = protocol.load_module("${String(
model
)}", location="${String(slotName)}")`,
loadStatement,
`labware_${labwareCount}.set_offset(x=${String(
x.toFixed(2)
)}, y=${String(y.toFixed(2))}, z=${String(z.toFixed(2))})`,
'',
]
}
} else if (command.commandType === 'loadModule') {
if (command.result == null) return acc
// load module on deck
const moduleVariable = `module_${
Object.keys(moduleVariableById).length + 1
}`
moduleVariableById = {
...moduleVariableById,
[command.result.moduleId]: moduleVariable,
}
const { model } = command.params
const { slotName } = command.params.location
addendum = [
`${moduleVariable} = protocol.load_module("${String(
model
)}", location="${String(slotName)}")`,
'',
]
}

return addendum != null ? [...acc, ...addendum] : acc
},
[]
)
return addendum != null ? [...acc, ...addendum] : acc
}, [])

return loadCommandLines.reduce<string>((acc, line) => {
if (mode === 'jupyter') {
Expand Down
25 changes: 16 additions & 9 deletions app/src/molecules/PythonLabwareOffsetSnippet/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ import * as React from 'react'
import styled from 'styled-components'
import { TYPOGRAPHY, SPACING, BORDERS } from '@opentrons/components'
import { createSnippet } from './createSnippet'
import type { CompletedProtocolAnalysis } from '@opentrons/shared-data'
import type { LabwareOffset } from '@opentrons/api-client'
import type { LabwareOffsetCreateData } from '@opentrons/api-client'
import type {
LoadedLabware,
LoadedModule,
RunTimeCommand,
} from '@opentrons/shared-data'

const JsonTextArea = styled.textarea`
min-height: 12vh;
min-height: 28vh;
width: 100%;
background-color: #f8f8f8;
border: ${BORDERS.lineBorder};
Expand All @@ -19,19 +23,22 @@ const JsonTextArea = styled.textarea`
`
interface PythonLabwareOffsetSnippetProps {
mode: 'jupyter' | 'cli'
protocol: CompletedProtocolAnalysis | null
labwareOffsets: LabwareOffset[] | null
commands: RunTimeCommand[]
labware: LoadedLabware[]
modules: LoadedModule[]
labwareOffsets: LabwareOffsetCreateData[] | null
}

export function PythonLabwareOffsetSnippet(
props: PythonLabwareOffsetSnippetProps
): JSX.Element | null {
const { protocol, labwareOffsets, mode } = props
const { commands, labware, modules, labwareOffsets, mode } = props
const [snippet, setSnippet] = React.useState<string | null>(null)

React.useEffect(() => {
if (protocol != null && labwareOffsets != null) {
setSnippet(createSnippet(mode, protocol, labwareOffsets))
if (labware.length > 0 && labwareOffsets != null) {
setSnippet(
createSnippet(mode, commands, labware, modules, labwareOffsets)
)
}
}, [mode, JSON.stringify(labwareOffsets)])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ describe('ApplyHistoricOffsets', () => {
]}
setShouldApplyOffsets={mockSetShouldApplyOffsets}
shouldApplyOffsets
commands={[]}
labware={[]}
modules={[]}
{...props}
/>,
{ i18nInstance: i18n }
Expand Down
31 changes: 26 additions & 5 deletions app/src/organisms/ApplyHistoricOffsets/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react'
import { useSelector } from 'react-redux'
import pick from 'lodash/pick'
import { useTranslation } from 'react-i18next'
import {
Flex,
Expand All @@ -21,6 +22,11 @@ import { LabwareOffsetTable } from './LabwareOffsetTable'
import { CheckboxField } from '../../atoms/CheckboxField'
import { getIsLabwareOffsetCodeSnippetsOn } from '../../redux/config'
import type { LabwareOffset } from '@opentrons/api-client'
import type {
LoadedLabware,
LoadedModule,
RunTimeCommand,
} from '@opentrons/shared-data'

const HOW_OFFSETS_WORK_SUPPORT_URL =
'https://support.opentrons.com/s/article/How-Labware-Offsets-work-on-the-OT-2'
Expand All @@ -33,11 +39,21 @@ interface ApplyHistoricOffsetsProps {
offsetCandidates: OffsetCandidate[]
shouldApplyOffsets: boolean
setShouldApplyOffsets: (shouldApplyOffsets: boolean) => void
commands: RunTimeCommand[]
labware: LoadedLabware[]
modules: LoadedModule[]
}
export function ApplyHistoricOffsets(
props: ApplyHistoricOffsetsProps
): JSX.Element {
const { offsetCandidates, shouldApplyOffsets, setShouldApplyOffsets } = props
const {
offsetCandidates,
shouldApplyOffsets,
setShouldApplyOffsets,
labware,
modules,
commands,
} = props
const [showOffsetDataModal, setShowOffsetDataModal] = React.useState(false)
const { t } = useTranslation('labware_position_check')
const isLabwareOffsetCodeSnippetsOn = useSelector(
Expand All @@ -46,15 +62,19 @@ export function ApplyHistoricOffsets(
const JupyterSnippet = (
<PythonLabwareOffsetSnippet
mode="jupyter"
labwareOffsets={null} // todo (jb 2-15-23) update the values passed in as part of the snippet updates
protocol={null} // todo (jb 2-15-23) update the values passed in as part of the snippet updates
labwareOffsets={offsetCandidates.map(o =>
pick(o, ['definitionUri', 'vector', 'location'])
)}
{...{ labware, modules, commands }}
/>
)
const CommandLineSnippet = (
<PythonLabwareOffsetSnippet
mode="cli"
labwareOffsets={null} // todo (jb 2-15-23) update the values passed in as part of the snippet updates
protocol={null} // todo (jb 2-15-23) update the values passed in as part of the snippet updates
labwareOffsets={offsetCandidates.map(o =>
pick(o, ['definitionUri', 'vector', 'location'])
)}
{...{ labware, modules, commands }}
/>
)
return (
Expand Down Expand Up @@ -97,6 +117,7 @@ export function ApplyHistoricOffsets(
: t('robot_has_no_offsets_from_previous_runs')}
</StyledText>
<Link
external
css={TYPOGRAPHY.linkPSemiBold}
marginTop={SPACING.spacing3}
href={HOW_OFFSETS_WORK_SUPPORT_URL}
Expand Down
Loading

0 comments on commit 6147fb9

Please sign in to comment.