Skip to content

Commit

Permalink
fix(protocol-designer): fix serialized name in ingred list (#2002)
Browse files Browse the repository at this point in the history
- factor apart utils
- refactor + write tests for sortWells fn
- use sortWells where missing
- remove vestigal serializedName code

Closes #1294
  • Loading branch information
IanLondon authored Aug 3, 2018
1 parent e80fd25 commit d19d29b
Show file tree
Hide file tree
Showing 12 changed files with 85 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
lost-column: 4/16;
}

.serialize_name {
.individualize {
lost-column: 6/16;
}

Expand Down
19 changes: 5 additions & 14 deletions protocol-designer/src/components/IngredientPropertiesForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,7 @@ class IngredientPropertiesForm extends React.Component<Props, State> {
name: null,
volume: null,
description: null,
individualize: false,
serializeName: null
individualize: false
},
commonIngredGroupId: null
}
Expand Down Expand Up @@ -144,16 +143,15 @@ class IngredientPropertiesForm extends React.Component<Props, State> {
const allIngredientGroupFields = (nextIngredGroupFields || this.props.allIngredientGroupFields || {})

if (ingredGroupId && ingredGroupId in allIngredientGroupFields) {
const {name, volume, description, individualize, serializeName} = this.state.input
const {name, volume, description, individualize} = this.state.input
const newIngredFields = allIngredientGroupFields[ingredGroupId]
this.setState({
...this.state,
input: {
name: newIngredFields.name || name,
volume: newIngredFields.volume || volume,
description: newIngredFields.description || description,
individualize: newIngredFields.individualize || individualize,
serializeName: newIngredFields.serializeName || serializeName
individualize: newIngredFields.individualize || individualize
}
}, cb)
} else {
Expand All @@ -168,8 +166,7 @@ class IngredientPropertiesForm extends React.Component<Props, State> {
name: null,
volume: null,
description: null,
individualize: false,
serializeName: null
individualize: false
}
}, cb)
}
Expand Down Expand Up @@ -293,19 +290,13 @@ class IngredientPropertiesForm extends React.Component<Props, State> {

<FormGroup
label={'\u00A0'} // non-breaking space
className={styles.serialize_name}
className={styles.individualize}
>
<Field
label='Serialize'
accessor='individualize'
type='checkbox'
/>
{/* TODO: Ian 2018-06-01 remove all remnants of this text field see issue #1294
{individualize && <Field
accessor='serializeName'
placeholder='i.e. sample'
className={styles.serialize_name_field}
/>} */}
</FormGroup>

{showIngredientDropdown && <FormGroup label={ingredDropdownLabel} className={styles.existing_ingred_field}>
Expand Down
10 changes: 6 additions & 4 deletions protocol-designer/src/components/IngredientsList.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import React from 'react'

import {IconButton, SidePanel, swatchColors} from '@opentrons/components'
import {sortWells} from '../utils'
import {PDTitledList, PDListItem} from './lists'
import stepItemStyles from './steplist/StepItem.css'
import StepDescription from './StepDescription'
Expand Down Expand Up @@ -48,11 +49,12 @@ class IngredGroupCard extends React.Component<CardProps, CardState> {
groupId,
labwareWellContents
} = this.props
const {serializeName, individualize, description, name} = ingredGroup
const {individualize, description, name} = ingredGroup
const {isExpanded} = this.state

const wellsWithIngred = Object.keys(labwareWellContents).filter(well =>
labwareWellContents[well][groupId])
const wellsWithIngred = Object.keys(labwareWellContents)
.sort(sortWells)
.filter(well => labwareWellContents[well][groupId])

if (wellsWithIngred.length < 1) {
// do not show ingred card if it has no instances for this labware
Expand Down Expand Up @@ -89,7 +91,7 @@ class IngredGroupCard extends React.Component<CardProps, CardState> {

return <IngredIndividual key={well}
name={individualize
? `${serializeName || 'Sample'} ${i + 1}` // TODO IMMED SORT AND NUMBER
? `${ingredGroup.name || ''} ${i + 1}`
: ''
}
wellName={well}
Expand Down
6 changes: 3 additions & 3 deletions protocol-designer/src/components/WellToolTip.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ type Props = {
wellName: string,

concentration?: string,
ingredientNum?: number,
serializeName?: string
ingredientNum?: number
}
}

// TODO: Ian 2018-07-02 use or lose this component!
export default function WellToolTip (props: Props) {
const wellContent = props.wellContent
return (
Expand All @@ -26,7 +26,7 @@ export default function WellToolTip (props: Props) {
{wellContent.wellName}
</div>
{wellContent.individualize && <div className={styles.instance_name}>
{wellContent.serializeName || 'Sample'} {wellContent.ingredientNum}
{wellContent.name || ''} {wellContent.ingredientNum}
</div>}
<div>
{wellContent.volume} uL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,6 @@ describe.skip('COPY_LABWARE action', () => {
describe('EDIT_INGREDIENT action', () => {
const ingredFields = {
name: 'Cool Ingredient',
serializeName: null,
volume: 250,
description: 'far out!',
individualize: false
Expand Down Expand Up @@ -211,7 +210,6 @@ describe('EDIT_INGREDIENT action', () => {
type: 'EDIT_INGREDIENT',
payload: {
name: 'Cool Ingredient',
serializeName: false,
volume: 250,
description: 'far out!',
individualize: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ const baseIngredFields = {
groupId: '0',
name: 'Some Ingred',
description: null,
individualize: false,
serializeName: null
individualize: false
}

const allIngredientsXXSingleIngred = {
Expand Down
7 changes: 3 additions & 4 deletions protocol-designer/src/labware-ingred/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import reduce from 'lodash/reduce'
import isEmpty from 'lodash/isEmpty'

import {sortedSlotnames, FIXED_TRASH_ID} from '../../constants.js'
import {uuid} from '../../utils.js'
import {uuid} from '../../utils'

import type {DeckSlot} from '@opentrons/components'

Expand Down Expand Up @@ -181,12 +181,11 @@ export const savedLabware = handleActions({
type IngredientsState = IngredientGroups
export const ingredients = handleActions({
EDIT_INGREDIENT: (state, action: EditIngredient) => {
const {groupId, description, individualize, name, serializeName} = action.payload
const {groupId, description, individualize, name} = action.payload
const ingredFields: IngredientInstance = {
description,
individualize,
name,
serializeName
name
}

return {
Expand Down
3 changes: 1 addition & 2 deletions protocol-designer/src/labware-ingred/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ export type IngredInputs = {
name: string | null,
volume: number | null,
description: string | null,
individualize: boolean,
serializeName: string | null
individualize: boolean
}

export type IngredInputFields = $Exact<IngredInputs>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ const baseIngredFields = {
groupId: '0',
name: 'Some Ingred',
description: null,
individualize: false,
serializeName: null
individualize: false
}

const containerState = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// @flow
import uuidv1 from 'uuid/v1'
import {wellNameSplit} from '@opentrons/components'
import type {BoundingRect, GenericRect} from './collision-types'
import type {Wells} from './labware-ingred/types'
import type {BoundingRect, GenericRect} from '../collision-types'
import type {Wells} from '../labware-ingred/types'

export type FormConnectorFactory<F> = (
handleChange: (accessor: F) => (e: SyntheticInputEvent<*>) => mixed,
Expand Down Expand Up @@ -101,3 +101,37 @@ export const getCollidingWells = (rectPositions: GenericRect, selectableClassnam

return collidedWellData
}

function _parseWell (well: string): ([string, number]) {
const res = well.match(/([A-Z]+)(\d+)/)
const letters = res && res[1]
const number = res && parseInt(res[2])

if (!letters || number == null || Number.isNaN(number)) {
console.warn(`Could not parse well ${well}. Got letters: "${letters || 'void'}", number: "${number || 'void'}"`)
return ['', NaN]
}

return [letters, number]
}

// TODO: Ian 2018-08-03 use well ordering in shared-data?
/** A compareFunction for sorting an array of well names
* Goes down the columns (A1 to H1 on 96 plate)
* Then L to R across rows (1 to 12 on 96 plate)
*/
export function sortWells (a: string, b: string): number {
const [letterA, numberA] = _parseWell(a)
const [letterB, numberB] = _parseWell(b)

if (numberA !== numberB) {
return (numberA > numberB) ? 1 : -1
}

if (letterA.length !== letterB.length) {
// Eg 'B' vs 'AA'
return (letterA.length > letterB.length) ? 1 : -1
}

return (letterA > letterB) ? 1 : -1
}
26 changes: 26 additions & 0 deletions protocol-designer/src/utils/test/sortWells.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// @flow
import {sortWells} from '..'

describe('sortWells', () => {
test('single letters', () => {
const input = ['A12', 'A2', 'B1', 'B12', 'A1']
const expected = [
'A1', 'B1',
'A2',
'A12', 'B12'
]
const result = [...input].sort(sortWells)
expect(result).toEqual(expected)
})

// just in case we get a 1536-well plate
test('double letters', () => {
const input = ['AB12', 'A12', 'B12', 'Z12', 'AA12', 'AB1', 'Z1', 'A1', 'B1', 'AA1']
const expected = [
'A1', 'B1', 'Z1', 'AA1', 'AB1',
'A12', 'B12', 'Z12', 'AA12', 'AB12'
]
const result = [...input].sort(sortWells)
expect(result).toEqual(expected)
})
})
31 changes: 2 additions & 29 deletions protocol-designer/src/well-selection/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import {createSelector} from 'reselect'
import reduce from 'lodash/reduce'
import {wellSetToWellObj} from './utils'
import {sortWells} from '../utils'
import {computeWellAccess} from '@opentrons/shared-data'
import type {BaseState, Selector} from '../types'
import type {Wells} from '../labware-ingred/types'
Expand Down Expand Up @@ -59,35 +60,7 @@ const getHighlightedWells: Selector<Wells> = createSelector(

const selectedWellNames: Selector<Array<string>> = createSelector(
(state: BaseState) => rootSelector(state).selectedWells.selected,
// TODO LATER Ian 2018-04-19 this will do different sort orders
// depending on selected well ordering (row-wise R2L top to bottom, etc)
// PS don't forget multi-letter wells like 'AA11' (1536-well)
selectedWells => Object.keys(selectedWells).sort((a, b) => {
function parseWell (well: string): ([string, number]) {
const letterMatches = well.match(/\D+/)
const numberMatches = well.match(/\d+/)

return [
(letterMatches && letterMatches[0]) || '',
(numberMatches && numberMatches[0] && parseInt(numberMatches[0])) || NaN
]
}

const [letterA, numberA] = parseWell(a)
const [letterB, numberB] = parseWell(b)

// this sort is top to bottom (down column), then left to right (across row)
if (numberA !== numberB) {
return (numberA > numberB) ? 1 : -1
}

if (letterA.length !== letterB.length) {
// Eg 'B' vs 'AA'
return (letterA.length > letterB.length) ? 1 : -1
}

return (letterA > letterB) ? 1 : -1
})
selectedWells => Object.keys(selectedWells).sort(sortWells)
)

export default {
Expand Down

0 comments on commit d19d29b

Please sign in to comment.