Skip to content

Commit

Permalink
feat(protocol-designer): add load liquid commands (#9923)
Browse files Browse the repository at this point in the history
* feat(protocol-designer): add load liquid commands

#9702
  • Loading branch information
shlokamin authored Apr 13, 2022
1 parent 0e1f615 commit 5b003f5
Show file tree
Hide file tree
Showing 14 changed files with 3,159 additions and 122 deletions.
2,695 changes: 2,695 additions & 0 deletions protocol-designer/fixtures/protocol/5/multipleLiquids.json

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions protocol-designer/src/file-data/__tests__/createFile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
fixtureP300Single,
} from '@opentrons/shared-data/pipette/fixtures/name'
import { LabwareDefinition2 } from '@opentrons/shared-data'
import { getLoadLiquidCommands } from '../../load-file/migration/utils/getLoadLiquidCommands'
import { createFile, getLabwareDefinitionsInUse } from '../selectors'
import {
fileMetadata,
Expand All @@ -29,6 +30,12 @@ import {
} from '../../../../step-generation/src/types'
import { LabwareDefByDefURI } from '../../labware-defs'

jest.mock('../../load-file/migration/utils/getLoadLiquidCommands')

const mockGetLoadLiquidCommands = getLoadLiquidCommands as jest.MockedFunction<
typeof getLoadLiquidCommands
>

const getAjvValidator = (_protocolSchema: Record<string, any>) => {
const ajv = new Ajv({
allErrors: true,
Expand Down Expand Up @@ -57,6 +64,12 @@ const expectResultToMatchSchema = (
}

describe('createFile selector', () => {
beforeEach(() => {
mockGetLoadLiquidCommands.mockReturnValue([])
})
afterEach(() => {
jest.restoreAllMocks()
})
it('should return a schema-valid JSON V6 protocol', () => {
// @ts-expect-error(sa, 2021-6-15): resultFunc not part of Selector type
const result = createFile.resultFunc(
Expand All @@ -79,6 +92,10 @@ describe('createFile selector', () => {
// have the opportunity to validate their part of the schema
expect(!isEmpty(result.labware)).toBe(true)
expect(!isEmpty(result.pipettes)).toBe(true)
expect(mockGetLoadLiquidCommands).toHaveBeenCalledWith(
ingredients,
ingredLocations
)
})
})
describe('getLabwareDefinitionsInUse util', () => {
Expand Down
80 changes: 44 additions & 36 deletions protocol-designer/src/file-data/selectors/fileCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { uuid } from '../../utils'
import { selectors as ingredSelectors } from '../../labware-ingred/selectors'
import { selectors as stepFormSelectors } from '../../step-forms'
import { selectors as uiLabwareSelectors } from '../../ui/labware'
import { getLoadLiquidCommands } from '../../load-file/migration/utils/getLoadLiquidCommands'
import {
DEFAULT_MM_FROM_BOTTOM_ASPIRATE,
DEFAULT_MM_FROM_BOTTOM_DISPENSE,
Expand All @@ -32,8 +33,8 @@ import type {
CreateCommand,
ProtocolFile,
} from '@opentrons/shared-data/protocol/types/schemaV6'
import { Selector } from '../../types'
import {
import type { Selector } from '../../types'
import type {
LoadLabwareCreateCommand,
LoadModuleCreateCommand,
LoadPipetteCreateCommand,
Expand Down Expand Up @@ -108,6 +109,39 @@ export const createFile: Selector<ProtocolFile> = createSelector(
const { author, description, created } = fileMetadata
const name = fileMetadata.protocolName || 'untitled'
const lastModified = fileMetadata.lastModified
// TODO: Ian 2018-07-10 allow user to save steps in JSON file, even if those
// step never have saved forms.
// (We could just export the `steps` reducer, but we've sunset it)
const savedOrderedStepIds = orderedStepIds.filter(
stepId => savedStepForms[stepId]
)
const designerApplication = {
name: 'opentrons/protocol-designer',
version: applicationVersion,
data: {
_internalAppBuildDate,
defaultValues: {
// TODO: Ian 2019-06-13 load these into redux and always get them from redux, not constants.js
// This `defaultValues` key is not yet read by anything, but is populated here for auditability
// and so that later we can do #3587 without a PD migration
aspirate_mmFromBottom: DEFAULT_MM_FROM_BOTTOM_ASPIRATE,
dispense_mmFromBottom: DEFAULT_MM_FROM_BOTTOM_DISPENSE,
touchTip_mmFromTop: DEFAULT_MM_TOUCH_TIP_OFFSET_FROM_TOP,
blowout_mmFromTop: DEFAULT_MM_BLOWOUT_OFFSET_FROM_TOP,
},
pipetteTiprackAssignments: mapValues(
pipetteEntities,
(
p: typeof pipetteEntities[keyof typeof pipetteEntities]
): string | null | undefined => p.tiprackDefURI
),
dismissedWarnings,
ingredients,
ingredLocations,
savedStepForms,
orderedStepIds: savedOrderedStepIds,
},
}

const pipettes: ProtocolFile['pipettes'] = mapValues(
initialRobotState.pipettes,
Expand Down Expand Up @@ -179,6 +213,11 @@ export const createFile: Selector<ProtocolFile> = createSelector(
return loadLabwareCommand
}
)

const loadLiquidCommands = getLoadLiquidCommands(
ingredients,
ingredLocations
)
const modules: ProtocolFile['modules'] = mapValues(
moduleEntities,
(moduleEntity: ModuleEntity, moduleId: string) => ({
Expand All @@ -203,12 +242,7 @@ export const createFile: Selector<ProtocolFile> = createSelector(
return loadModuleCommand
}
)
// TODO: Ian 2018-07-10 allow user to save steps in JSON file, even if those
// step never have saved forms.
// (We could just export the `steps` reducer, but we've sunset it)
const savedOrderedStepIds = orderedStepIds.filter(
stepId => savedStepForms[stepId]
)

const labwareDefinitions = getLabwareDefinitionsInUse(
labwareEntities,
pipetteEntities,
Expand All @@ -218,7 +252,7 @@ export const createFile: Selector<ProtocolFile> = createSelector(
...loadPipetteCommands,
...loadLabwareCommands,
...loadModuleCommands,
// TODO: generate load liquid commands https://github.com/Opentrons/opentrons/issues/9702
...loadLiquidCommands,
]

const nonLoadCommands: CreateCommand[] = flatMap(
Expand All @@ -240,33 +274,7 @@ export const createFile: Selector<ProtocolFile> = createSelector(
subcategory: null,
tags: [],
},
designerApplication: {
name: 'opentrons/protocol-designer',
version: applicationVersion,
data: {
_internalAppBuildDate,
defaultValues: {
// TODO: Ian 2019-06-13 load these into redux and always get them from redux, not constants.js
// This `defaultValues` key is not yet read by anything, but is populated here for auditability
// and so that later we can do #3587 without a PD migration
aspirate_mmFromBottom: DEFAULT_MM_FROM_BOTTOM_ASPIRATE,
dispense_mmFromBottom: DEFAULT_MM_FROM_BOTTOM_DISPENSE,
touchTip_mmFromTop: DEFAULT_MM_TOUCH_TIP_OFFSET_FROM_TOP,
blowout_mmFromTop: DEFAULT_MM_BLOWOUT_OFFSET_FROM_TOP,
},
pipetteTiprackAssignments: mapValues(
pipetteEntities,
(
p: typeof pipetteEntities[keyof typeof pipetteEntities]
): string | null | undefined => p.tiprackDefURI
),
dismissedWarnings,
ingredients,
ingredLocations,
savedStepForms,
orderedStepIds: savedOrderedStepIds,
},
},
designerApplication,
robot: {
model: OT2_STANDARD_MODEL,
deckId: OT2_STANDARD_DECKID,
Expand Down
22 changes: 8 additions & 14 deletions protocol-designer/src/load-file/migration/6_0_0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { uuid } from '../../utils'
// and labware access parameters, renames AirGap to aspirate, and removes all temporal properties from labware, pipettes,
// and module keys such as slot, mount
// and renames well to wellName
import { getLoadLiquidCommands } from './utils/getLoadLiquidCommands'
import type {
LoadPipetteCreateCommand,
LoadModuleCreateCommand,
Expand All @@ -21,18 +22,7 @@ import type {
CreateCommand,
ProtocolFile,
} from '@opentrons/shared-data/protocol/types/schemaV6'

interface DesignerApplicationData {
ingredients: Record<
string,
{
name: string
description: string | null
serialize: boolean
liquidGroupId: string
}
>
}
import type { DesignerApplicationData } from './utils/getLoadLiquidCommands'

const PD_VERSION = '6.0.0'
const SCHEMA_VERSION = 6
Expand Down Expand Up @@ -85,7 +75,7 @@ const migrateCommands = (
export const migrateFile = (
appData: ProtocolFileV5<DesignerApplicationData>
): ProtocolFile => {
const { pipettes, labware, modules, commands } = appData
const { pipettes, labware, modules, commands, designerApplication } = appData
const loadPipetteCommands: LoadPipetteCreateCommand[] = map(
pipettes,
(pipette, pipetteId) => {
Expand Down Expand Up @@ -131,6 +121,10 @@ export const migrateFile = (
}
)

const loadLiquidCommands = getLoadLiquidCommands(
designerApplication?.data?.ingredients,
designerApplication?.data?.ingredLocations
)
const migratedV5Commands = migrateCommands(commands)

const liquids: ProtocolFile['liquids'] =
Expand Down Expand Up @@ -168,7 +162,7 @@ export const migrateFile = (
modules: migrateModules(appData.modules),
liquids,
commands: [
// TODO: generate load liquid commands https://github.com/Opentrons/opentrons/issues/9702
...loadLiquidCommands,
...loadPipetteCommands,
...loadModuleCommands,
...loadLabwareCommands,
Expand Down
31 changes: 28 additions & 3 deletions protocol-designer/src/load-file/migration/__tests__/6_0_0.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import { migrateFile } from '../6_0_0'
import { getLoadLiquidCommands } from '../utils/getLoadLiquidCommands'
import _oldProtocol from 'protocol-designer/fixtures/protocol/5/doItAllV5.json'
import type { ProtocolFile, ProtocolFileV5 } from '@opentrons/shared-data'
import type { ProtocolFileV5 } from '@opentrons/shared-data'

const oldProtocol = (_oldProtocol as unknown) as ProtocolFileV5<any>

jest.mock('../utils/getLoadLiquidCommands')

const mockGetLoadLiquidCommands = getLoadLiquidCommands as jest.MockedFunction<
typeof getLoadLiquidCommands
>

describe('v6 migration', () => {
let migratedFile = {} as ProtocolFile
beforeEach(() => {
migratedFile = migrateFile(oldProtocol)
mockGetLoadLiquidCommands.mockReturnValue([])
})
afterEach(() => {
jest.restoreAllMocks()
})
it('removes slot from modules and labware', () => {
const migratedFile = migrateFile(oldProtocol)

expect(
oldProtocol.modules[
'0b419310-75c7-11ea-b42f-4b64e50f43e5:magneticModuleType'
Expand All @@ -34,6 +45,7 @@ describe('v6 migration', () => {
).toBeUndefined()
})
it('removes mount from pipettes', () => {
const migratedFile = migrateFile(oldProtocol)
expect(
oldProtocol.pipettes['0b3f2210-75c7-11ea-b42f-4b64e50f43e5'].mount
).toEqual('left')
Expand All @@ -43,17 +55,20 @@ describe('v6 migration', () => {
).toBeUndefined()
})
it('adds deckId to Robot', () => {
const migratedFile = migrateFile(oldProtocol)
expect(oldProtocol.robot).toEqual({ model: 'OT-2 Standard' })
expect(migratedFile.robot).toEqual({
model: 'OT-2 Standard',
deckId: 'ot2_standard',
})
})
it('adds a liquids key', () => {
const migratedFile = migrateFile(oldProtocol)
const expectedLiquids = { '0': { displayName: 'Water', description: null } }
expect(migratedFile.liquids).toEqual(expectedLiquids)
})
it('creates loadModule commands', () => {
const migratedFile = migrateFile(oldProtocol)
const expectedLoadModuleCommands = [
{
key: expect.any(String),
Expand All @@ -80,6 +95,7 @@ describe('v6 migration', () => {
expect(loadModuleCommands).toEqual(expectedLoadModuleCommands)
})
it('creates loadPipette commands', () => {
const migratedFile = migrateFile(oldProtocol)
const expectedLoadPipetteCommands = [
{
key: expect.any(String),
Expand All @@ -96,6 +112,7 @@ describe('v6 migration', () => {
expect(loadPipetteCommands).toEqual(expectedLoadPipetteCommands)
})
it('creates loadLabware commands', () => {
const migratedFile = migrateFile(oldProtocol)
const loadLabwareCommands = migratedFile.commands.filter(
command => command.commandType === 'loadLabware'
)
Expand Down Expand Up @@ -140,7 +157,15 @@ describe('v6 migration', () => {
]
expect(loadLabwareCommands).toEqual(expectedLoadLabwareCommaands)
})
it('creates loadLiquid commands', () => {
migrateFile(oldProtocol)
expect(mockGetLoadLiquidCommands).toHaveBeenCalledWith(
_oldProtocol.designerApplication.data.ingredients,
_oldProtocol.designerApplication.data.ingredLocations
)
})
it('replaces air gap commands with aspirate commands', () => {
const migratedFile = migrateFile(oldProtocol)
const expectedConvertedAirgapCommands = [
{
key: expect.any(String), // no key used to exist in v5 commands
Expand Down
Loading

0 comments on commit 5b003f5

Please sign in to comment.