From d7f119aacc9b2f08fcec8348236605b820450f2c Mon Sep 17 00:00:00 2001 From: laura_danielle Date: Tue, 30 Jun 2020 09:42:03 -0400 Subject: [PATCH 01/18] Add tests and epic --- app/src/calibration/labware/actions.js | 45 ++++++++++ app/src/calibration/labware/constants.js | 15 ++++ .../fetchLabwareCalibrationsEpic.test.js | 88 +++++++++++++++++++ .../epic/fetchLabwareCalibrationsEpic.js | 55 ++++++++++++ app/src/calibration/labware/epic/index.js | 14 +++ app/src/calibration/labware/index.js | 0 app/src/calibration/labware/selectors.js | 0 app/src/calibration/labware/types.js | 24 +++++ app/src/calibration/reducer.js | 21 ++++- app/src/calibration/types.js | 1 + .../FileInfo/ProtocolLabwareCard.js | 10 ++- ...LabwareTable.js => ProtocolLabwareList.js} | 10 ++- 12 files changed, 276 insertions(+), 7 deletions(-) create mode 100644 app/src/calibration/labware/actions.js create mode 100644 app/src/calibration/labware/constants.js create mode 100644 app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js create mode 100644 app/src/calibration/labware/epic/fetchLabwareCalibrationsEpic.js create mode 100644 app/src/calibration/labware/epic/index.js create mode 100644 app/src/calibration/labware/index.js create mode 100644 app/src/calibration/labware/selectors.js create mode 100644 app/src/calibration/labware/types.js rename app/src/components/FileInfo/{LabwareTable.js => ProtocolLabwareList.js} (58%) diff --git a/app/src/calibration/labware/actions.js b/app/src/calibration/labware/actions.js new file mode 100644 index 00000000000..3547b83d6b9 --- /dev/null +++ b/app/src/calibration/labware/actions.js @@ -0,0 +1,45 @@ +// @flow + +import * as Constants from './constants' +import * as Types from './types' + +import type { + RobotApiRequestMeta, + RobotApiErrorResponse, +} from '../../robot-api/types' + +export const fetchAllLabwareCalibrations = ( + robotName: string +): Types.FetchCalibrationStatusAction => ({ + type: Constants.FETCH_ALL_LABWARE_CALIBRATIONS, + payload: { robotName }, + meta: {}, +}) + +export const fetchSingleLabwareCalibration = ( + robotName: string +): Types.FetchCalibrationStatusAction => ({ + type: Constants.FETCH_SINGLE_LABWARE_CALIBRATION, + payload: { robotName }, + meta: {}, +}) + +export const fetchLabwareCalibrationSuccess = ( + robotName: string, + calibrationStatus: Types.CalibrationStatus, + meta: RobotApiRequestMeta +): Types.FetchCalibrationStatusSuccessAction => ({ + type: Constants.FETCH_LABWARE_CALIBRATION_SUCCESS, + payload: { robotName, calibrationStatus }, + meta, +}) + +export const fetchLabwareCalibrationFailure = ( + robotName: string, + error: RobotApiErrorResponse, + meta: RobotApiRequestMeta +): Types.FetchCalibrationStatusFailureAction => ({ + type: Constants.FETCH_LABWARE_CALIBRATION_FAILURE, + payload: { robotName, error }, + meta, +}) diff --git a/app/src/calibration/labware/constants.js b/app/src/calibration/labware/constants.js new file mode 100644 index 00000000000..ee484081bb2 --- /dev/null +++ b/app/src/calibration/labware/constants.js @@ -0,0 +1,15 @@ +// @flow + +export const LABWARE_CALIBRATION_PATH: '/labware/calibrations' = + '/labware/calibrations' + +export const FETCH_ALL_LABWARE_CALIBRATIONS: 'calibration:FETCH_ALL_LABWARE_CALIBRATIONS' = + 'calibration:FETCH_ALL_LABWARE_CALIBRATIONS' +export const FETCH_SINGLE_LABWARE_CALIBRATION: 'calibration:FETCH_SINGLE_LABWARE_CALIBRATION' = + 'calibration:FETCH_SINGLE_LABWARE_CALIBRATION' + +export const FETCH_LABWARE_CALIBRATION_SUCCESS: 'calibration:FETCH_LABWARE_CALIBRATION_SUCCESS' = + 'calibration:FETCH_LABWARE_CALIBRATION_SUCCESS' + +export const FETCH_LABWARE_CALIBRATION_FAILURE: 'calibration:FETCH_LABWARE_CALIBRATION_FAILURE' = + 'calibration:FETCH_LABWARE_CALIBRATION_FAILURE' diff --git a/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js b/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js new file mode 100644 index 00000000000..89fa9e503be --- /dev/null +++ b/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js @@ -0,0 +1,88 @@ +// @flow +import { + setupEpicTestMocks, + runEpicTest, +} from '../../../../robot-api/__utils__' +import * as Fixtures from '../../../__fixtures__' +import * as Actions from '../../actions' +import { labwareCalibrationEpic } from '..' + +const makeTriggerActionAllCalibrations = robotName => + Actions.fetchAllLabwareCalibrations(robotName) +const makeTriggerActionSingleCalibrations = robotName => + Actions.fetchSingleLabwareCalibration(robotName) + +describe('fetch calibration status epic', () => { + afterEach(() => { + jest.resetAllMocks() + }) + + it('calls GET /calibration/status', () => { + const mocks = setupEpicTestMocks( + makeTriggerActionAllCalibrations, + Fixtures.mockFetchCalibrationStatusSuccess + ) + + runEpicTest(mocks, ({ hot, expectObservable, flush }) => { + const action$ = hot('--a', { a: mocks.action }) + const state$ = hot('s-s', { s: mocks.state }) + const output$ = labwareCalibrationEpic(action$, state$) + + expectObservable(output$) + flush() + + expect(mocks.fetchRobotApi).toHaveBeenCalledWith(mocks.robot, { + method: 'GET', + path: '/calibration/status', + }) + }) + }) + + it('maps successful response to FETCH_CALIBRATION_STATUS_SUCCESS', () => { + const mocks = setupEpicTestMocks( + makeTriggerAction, + Fixtures.mockFetchCalibrationStatusSuccess + ) + + runEpicTest(mocks, ({ hot, expectObservable }) => { + const action$ = hot('--a', { a: mocks.action }) + const state$ = hot('s-s', { s: mocks.state }) + const output$ = labwareCalibrationEpic(action$, state$) + + expectObservable(output$).toBe('--a', { + a: Actions.fetchCalibrationStatusSuccess( + mocks.robot.name, + Fixtures.mockFetchCalibrationStatusSuccess.body, + { + ...mocks.meta, + response: Fixtures.mockFetchCalibrationStatusSuccessMeta, + } + ), + }) + }) + }) + + it('maps failed response to FETCH_CALIBRATION_STATUS_FAILURE', () => { + const mocks = setupEpicTestMocks( + makeTriggerAction, + Fixtures.mockFetchCalibrationStatusFailure + ) + + runEpicTest(mocks, ({ hot, expectObservable }) => { + const action$ = hot('--a', { a: mocks.action }) + const state$ = hot('s-s', { s: mocks.state }) + const output$ = labwareCalibrationEpic(action$, state$) + + expectObservable(output$).toBe('--a', { + a: Actions.fetchCalibrationStatusFailure( + mocks.robot.name, + Fixtures.mockFetchCalibrationStatusFailure.body, + { + ...mocks.meta, + response: Fixtures.mockFetchCalibrationStatusFailureMeta, + } + ), + }) + }) + }) +}) diff --git a/app/src/calibration/labware/epic/fetchLabwareCalibrationsEpic.js b/app/src/calibration/labware/epic/fetchLabwareCalibrationsEpic.js new file mode 100644 index 00000000000..e87c3f95a6c --- /dev/null +++ b/app/src/calibration/labware/epic/fetchLabwareCalibrationsEpic.js @@ -0,0 +1,55 @@ +// @flow +import { ofType } from 'redux-observable' + +import { GET } from '../../../robot-api/constants' +import { mapToRobotApiRequest } from '../../../robot-api/operators' +import * as Actions from '../actions' +import * as Constants from '../constants' + +import type { + ActionToRequestMapper, + ResponseToActionMapper, +} from '../../../robot-api/operators' +import type { Epic } from '../../../types' +import type { FetchLabwareCalibrationAction } from '../types' + +const mapActionToRequest: ActionToRequestMapper = action => ({ + method: GET, + path: Constants.LABWARE_CALIBRATION_PATH, +}) + +const mapResponseToAction: ResponseToActionMapper = ( + response, + originalAction +) => { + const { host, body, ...responseMeta } = response + const meta = { ...originalAction.meta, response: responseMeta } + + return response.ok + ? Actions.fetchLabwareCalibrationSuccess(host.name, body, meta) + : Actions.fetchLabwareCalibrationFailure(host.name, body, meta) +} + +export const fetchAllLabwareCalibrationsEpic: Epic = (action$, state$) => { + return action$.pipe( + ofType(Constants.FETCH_ALL_LABWARE_CALIBRATIONS), + mapToRobotApiRequest( + state$, + a => a.payload.robotName, + mapActionToRequest, + mapResponseToAction + ) + ) +} + +export const fetchSingleLabwareCalibrationEpic: Epic = (action$, state$) => { + return action$.pipe( + ofType(Constants.FETCH_SINGLE_LABWARE_CALIBRATION), + mapToRobotApiRequest( + state$, + a => a.payload.robotName, + mapActionToRequest, + mapResponseToAction + ) + ) +} diff --git a/app/src/calibration/labware/epic/index.js b/app/src/calibration/labware/epic/index.js new file mode 100644 index 00000000000..45ae7702744 --- /dev/null +++ b/app/src/calibration/labware/epic/index.js @@ -0,0 +1,14 @@ +// @flow + +import { combineEpics } from 'redux-observable' +import { + fetchAllLabwareCalibrationsEpic, + fetchSingleLabwareCalibrationEpic, +} from './fetchLabwareCalibrationsEpic' + +import type { Epic } from '../../../types' + +export const labwareCalibrationEpic: Epic = combineEpics( + fetchAllLabwareCalibrationsEpic, + fetchSingleLabwareCalibrationEpic +) diff --git a/app/src/calibration/labware/index.js b/app/src/calibration/labware/index.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/src/calibration/labware/selectors.js b/app/src/calibration/labware/selectors.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/src/calibration/labware/types.js b/app/src/calibration/labware/types.js new file mode 100644 index 00000000000..7d2bf569cac --- /dev/null +++ b/app/src/calibration/labware/types.js @@ -0,0 +1,24 @@ +// @flow + +import type { + RobotApiRequestMeta, + RobotApiErrorResponse, + } from '../robot-api/types' + + import type { CalibrationStatus } from './types' + + import typeof { + FETCH_CALIBRATION_STATUS, + FETCH_CALIBRATION_STATUS_SUCCESS, + FETCH_CALIBRATION_STATUS_FAILURE, + } from './constants' + + export * from './api-types' + + +export type FetchLabwareCalibrationAction = {| + type: FETCH_CALIBRATION_STATUS, + payload: {| robotName: string |}, + meta: RobotApiRequestMeta, + |} + \ No newline at end of file diff --git a/app/src/calibration/reducer.js b/app/src/calibration/reducer.js index 4707a45ac09..4e0062e6388 100644 --- a/app/src/calibration/reducer.js +++ b/app/src/calibration/reducer.js @@ -1,5 +1,6 @@ // @flow import * as Constants from './constants' +import * as LabwareConstants from './labware/constants' import type { Action } from '../types' import type { CalibrationState } from './types' @@ -13,8 +14,26 @@ export function calibrationReducer( switch (action.type) { case Constants.FETCH_CALIBRATION_STATUS_SUCCESS: { const { robotName, calibrationStatus } = action.payload - return { ...state, [robotName]: { calibrationStatus } } + return { + ...state, + [robotName]: { + ...(state[robotName] ?? {}), + calibrationStatus, + }, + } } + case LabwareConstants.FETCH_LABWARE_CALIBRATION_SUCCESS: { + const { robotName, labwareCalibrations } = action.payload + return { + ...state, + [robotName]: { + ...(state[robotName] ?? {}), + labwareCalibrations, + }, + } + } + // create new action type of labware calibration + // don't follow exactly above because it would delete cal status. } return state } diff --git a/app/src/calibration/types.js b/app/src/calibration/types.js index b54912fcab7..5a38371c28e 100644 --- a/app/src/calibration/types.js +++ b/app/src/calibration/types.js @@ -43,6 +43,7 @@ export type CalibrationAction = export type PerRobotCalibrationState = $ReadOnly<{| calibrationStatus: CalibrationStatus, + // add stuff here |}> export type CalibrationState = $ReadOnly<{ diff --git a/app/src/components/FileInfo/ProtocolLabwareCard.js b/app/src/components/FileInfo/ProtocolLabwareCard.js index 8728510f307..932ca0c5494 100644 --- a/app/src/components/FileInfo/ProtocolLabwareCard.js +++ b/app/src/components/FileInfo/ProtocolLabwareCard.js @@ -6,7 +6,7 @@ import countBy from 'lodash/countBy' import { selectors as robotSelectors } from '../../robot' import { InfoSection } from './InfoSection' -import { LabwareTable } from './LabwareTable' +import { ProtocolLabwareList } from './ProtocolLabwareList' import type { State, Dispatch } from '../../types' import type { Labware } from '../../robot' @@ -41,7 +41,13 @@ function ProtocolLabwareCardComponent(props: Props) { return ( - {labwareList} + {labwareCount.map(labware => ( + + {itemProps.displayName} + + ))} ) } diff --git a/app/src/components/FileInfo/LabwareTable.js b/app/src/components/FileInfo/ProtocolLabwareList.js similarity index 58% rename from app/src/components/FileInfo/LabwareTable.js rename to app/src/components/FileInfo/ProtocolLabwareList.js index 8785dd9e4eb..eed1c0d8c15 100644 --- a/app/src/components/FileInfo/LabwareTable.js +++ b/app/src/components/FileInfo/ProtocolLabwareList.js @@ -2,13 +2,15 @@ import * as React from 'react' import styles from './styles.css' -export type LabwareTableProps = {| +export type ProtocolLabwareListProps = {| children: React.Node, |} -export function LabwareTable(props: LabwareTableProps): React.Node { +export function ProtocolLabwareList( + props: ProtocolLabwareListProps +): React.Node { return ( - +
@@ -16,6 +18,6 @@ export function LabwareTable(props: LabwareTableProps): React.Node { {props.children} -
Type
+ ) } From 4116e4273916b5aa75d6b5de58ced862d4b08f5a Mon Sep 17 00:00:00 2001 From: laura_danielle Date: Thu, 9 Jul 2020 11:39:51 -0400 Subject: [PATCH 02/18] Refactor components on the file info page --- app/src/components/FileInfo/Continue.js | 16 +++- .../FileInfo/ProtocolLabwareCard.js | 87 ++++++++++--------- .../FileInfo/ProtocolLabwareList.js | 76 +++++++++++++--- .../FileInfo/__tests__/continue.test.js | 26 ++++++ .../__tests__/protocollabwarelist.test.js | 54 ++++++++++++ app/src/components/FileInfo/index.js | 50 ++++++++++- app/src/components/FileInfo/styles.css | 5 +- .../components/layout/SectionContentFlex.js | 43 +++++++++ app/src/components/layout/index.js | 1 + 9 files changed, 294 insertions(+), 64 deletions(-) create mode 100644 app/src/components/FileInfo/__tests__/continue.test.js create mode 100644 app/src/components/FileInfo/__tests__/protocollabwarelist.test.js create mode 100644 app/src/components/layout/SectionContentFlex.js diff --git a/app/src/components/FileInfo/Continue.js b/app/src/components/FileInfo/Continue.js index bbb8b6ff2d3..8f29bb7bd60 100644 --- a/app/src/components/FileInfo/Continue.js +++ b/app/src/components/FileInfo/Continue.js @@ -3,12 +3,20 @@ import * as React from 'react' import { useSelector } from 'react-redux' import { Link } from 'react-router-dom' -import { getCalibrateLocation } from '../../nav' +import { getCalibrateLocation, getRunLocation } from '../../nav' import { PrimaryButton, HoverTooltip } from '@opentrons/components' import styles from './styles.css' -export function Continue(): React.Node { - const { path, disabledReason } = useSelector(getCalibrateLocation) +type ContinueProps = {| + labwareCalibrated: boolean, +|} + +export function Continue({ labwareCalibrated }: ContinueProps): React.Node { + const buttonText = labwareCalibrated + ? 'Proceed to Run' + : 'Proceed to Calibrate' + const selector = labwareCalibrated ? getRunLocation : getCalibrateLocation + const { path, disabledReason } = useSelector(selector) // TODO(mc, 2019-11-26): tooltip positioning is all messed up with this component return ( @@ -24,7 +32,7 @@ export function Continue(): React.Node { disabled={Boolean(disabledReason)} className={styles.continue_button} > - Proceed to Calibrate + {buttonText} )} diff --git a/app/src/components/FileInfo/ProtocolLabwareCard.js b/app/src/components/FileInfo/ProtocolLabwareCard.js index 932ca0c5494..8e34e20694d 100644 --- a/app/src/components/FileInfo/ProtocolLabwareCard.js +++ b/app/src/components/FileInfo/ProtocolLabwareCard.js @@ -1,58 +1,61 @@ // @flow // setup labware component import * as React from 'react' -import { connect } from 'react-redux' -import countBy from 'lodash/countBy' +import round from 'lodash/round' + +import { ALIGN_CENTER, FONT_WEIGHT_SEMIBOLD } from '@opentrons/components' -import { selectors as robotSelectors } from '../../robot' import { InfoSection } from './InfoSection' import { ProtocolLabwareList } from './ProtocolLabwareList' -import type { State, Dispatch } from '../../types' -import type { Labware } from '../../robot' - -type SP = {| labware: Array |} - -type Props = {| ...SP, dispatch: Dispatch |} +type ProtocolLabwareProps = {| + labware: { string: number }, + labwareCalibrations: Object, // Note this should be { string: LabwareCalibrationObjects }, but flow was not passing +|} const TITLE = 'Required Labware' -export const ProtocolLabwareCard: React.AbstractComponent<{||}> = connect< - Props, - {||}, - _, - _, - _, - _ ->(mapStateToProps)(ProtocolLabwareCardComponent) - -function ProtocolLabwareCardComponent(props: Props) { - const { labware } = props - - if (labware.length === 0) return null - - const labwareCount = countBy(labware, 'type') - const labwareList = Object.keys(labwareCount).map(type => ( - - {type} - {`x${labwareCount[type]}`} - - )) +export function ProtocolLabwareCard({ + labware, + labwareCalibrations, +}: ProtocolLabwareProps): React.Node { + if (Object.keys(labware).length === 0) return null + + const labwareCalibration = Object.keys(labware).map(function(type, index) { + const currentLabware = labwareCalibrations[type]?.attributes + if (currentLabware) { + const offset = currentLabware?.calibrationData?.offset.value + const X = round(offset[0], 2) + const Y = round(offset[1], 2) + const Z = round(offset[2], 2) + return ( + + X + {X} + Y + {Y} + Z + {Z} + + ) + } else { + return ( + + Not yet calibrated + + ) + } + }) + + const labwareQuantity = Object.keys(labware).map(type => `x${labware[type]}`) return ( - {labwareCount.map(labware => ( - - {itemProps.displayName} - - ))} + ) } -function mapStateToProps(state: State): SP { - return { - labware: robotSelectors.getLabware(state), - } -} diff --git a/app/src/components/FileInfo/ProtocolLabwareList.js b/app/src/components/FileInfo/ProtocolLabwareList.js index eed1c0d8c15..09a034625ba 100644 --- a/app/src/components/FileInfo/ProtocolLabwareList.js +++ b/app/src/components/FileInfo/ProtocolLabwareList.js @@ -1,23 +1,73 @@ // @flow import * as React from 'react' + +import { + DIRECTION_COLUMN, + Flex, + Text, + Icon, + Tooltip, + useHoverTooltip, + FLEX_AUTO, + JUSTIFY_SPACE_BETWEEN, + FONT_SIZE_BODY_1, + FONT_WEIGHT_REGULAR, + C_DARK_GRAY, + TOOLTIP_AUTO, + SPACING_1, +} from '@opentrons/components' +import { SectionContentFlex } from '../layout' import styles from './styles.css' export type ProtocolLabwareListProps = {| - children: React.Node, + labware: Array, + quantity: Array, + calibration: Array, |} -export function ProtocolLabwareList( - props: ProtocolLabwareListProps -): React.Node { +export function ProtocolLabwareList({ + labware, + quantity, + calibration, +}: ProtocolLabwareListProps): React.Node { + const [targetProps, tooltipProps] = useHoverTooltip({ + placement: TOOLTIP_AUTO, + }) + const iconComponent = + const toolTipComponent = ( + + {'calibrated offset from labware origin point'} + + ) return ( -
- - - Type - Quantity - - {props.children} - -
+ + + {labware.map(name => ( + {name} + ))} + + + {quantity.map((item, index) => ( + {item} + ))} + + + + {calibration} +
+
+
) } diff --git a/app/src/components/FileInfo/__tests__/continue.test.js b/app/src/components/FileInfo/__tests__/continue.test.js new file mode 100644 index 00000000000..40b48749a6c --- /dev/null +++ b/app/src/components/FileInfo/__tests__/continue.test.js @@ -0,0 +1,26 @@ +// @flow +import * as React from 'react' +import { mount } from 'enzyme' + +import { PrimaryButton } from '@opentrons/components' +import { Continue } from '../Continue' + +describe('Continue to run or calibration button component', () => { + const render = (status: boolean = false) => { + return mount() + } + + it('Default button renders to continue to labware when not all labware is calibrated', () => { + const wrapper = render() + const button = wrapper.find(PrimaryButton) + console.log(button) + expect(button.children).toEqual('Proceed to Calibrate') + }) + + it('renders nothing when calibration is OK', () => { + const wrapper = render(true) + const button = wrapper.find(PrimaryButton) + console.log(button.children) + expect(button.children).toEqual('Proceed to Calibrate') + }) +}) diff --git a/app/src/components/FileInfo/__tests__/protocollabwarelist.test.js b/app/src/components/FileInfo/__tests__/protocollabwarelist.test.js new file mode 100644 index 00000000000..58ae8fd179f --- /dev/null +++ b/app/src/components/FileInfo/__tests__/protocollabwarelist.test.js @@ -0,0 +1,54 @@ +// @flow +import * as React from 'react' +import { mount } from 'enzyme' + +import { Flex, Icon } from '@opentrons/components' +import { ProtocolLabwareList } from '../ProtocolLabwareList' + +import { SectionContentFlex } from '../../layout' + +describe('Protocol Labware List Component', () => { + const render = ( + labware: Array, + quantity: Array, + calibration: Array + ) => { + return mount( + + ) + } + + it('renders nothing when no labware exists in the protocol', () => { + const wrapper = render([], [], []) + expect(wrapper).toEqual({}) + }) + + it('renders three columns when a labware exists', () => { + const rowToRender = ( + + Not yet calibrated + + ) + const wrapper = render(['opentrons_labware'], ['x1'], [rowToRender]) + const flex = wrapper.find(Flex) + const sections = flex.find(SectionContentFlex) + console.log(sections) + expect(wrapper).toEqual({}) + }) + + it('renders a table with values when given calibration data', () => { + const rowToRender = Not yet calibrated + // const wrapper = render(['opentrons_labware'], ['x1'], [rowToRender]) + // const parent = wrapper.find(Flex).first() + // const icon = wrapper.find(Icon) + + // expect(parent.prop('color')).toBe(COLOR_WARNING) + // expect(icon.prop('name')).toEqual('alert-circle') + // expect(wrapper.html()).toMatch(/not yet been calibrated/i) + // expect(wrapper.html()).toMatch(/please perform a deck calibration/i) + }) +}) diff --git a/app/src/components/FileInfo/index.js b/app/src/components/FileInfo/index.js index f70f4a4c549..f98c76d94dc 100644 --- a/app/src/components/FileInfo/index.js +++ b/app/src/components/FileInfo/index.js @@ -1,5 +1,11 @@ // @flow import * as React from 'react' +import { useSelector, useDispatch } from 'react-redux' + +import filter from 'lodash/filter' +import every from 'lodash/every' +import countBy from 'lodash/countBy' +import keyBy from 'lodash/keyBy' // TODO(mc, 2018-09-13): these aren't cards; rename import { InformationCard } from './InformationCard' @@ -10,6 +16,16 @@ import { Continue } from './Continue' import { UploadError } from '../UploadError' import styles from './styles.css' +import { selectors as robotSelectors } from '../../robot' +import { + selectors as labwareCalibrationSelectors, + actions as labwareActions, +} from '../../calibration/labware' + +import type { State, Dispatch } from '../../types' +// import type { Labware } from '../../robot' +// import type { SingleLabwareCalibration } from '../../calibration/api-types' + import type { Robot } from '../../discovery/types' const NO_STEPS_MESSAGE = `This protocol has no steps in it - there's nothing for your robot to do! Your protocol needs at least one aspirate/dispense to import properly` @@ -22,21 +38,51 @@ export type FileInfoProps = {| |} export function FileInfo(props: FileInfoProps): React.Node { + const dispatch = useDispatch() const { robot, sessionLoaded, sessionHasSteps } = props + const { name: robotName } = robot + React.useEffect(() => { + dispatch(labwareActions.fetchAllLabwareCalibrations(robotName)) + }, [dispatch, robotName]) let uploadError = props.uploadError + const labware = useSelector((state: State) => + robotSelectors.getLabware(state) + ) + const labwareCalibrations = useSelector((state: State) => + labwareCalibrationSelectors.getListOfLabwareCalibrations(state, robotName) + ) + if (sessionLoaded && !uploadError && !sessionHasSteps) { uploadError = { message: NO_STEPS_MESSAGE } } + const labwareCount = countBy(labware, 'type') + + const calibrations = filter(labwareCalibrations, function(l) { + return Object.keys(labwareCount).includes(l?.attributes.loadName) + }) + + const calibrationLoadNamesMap = keyBy(calibrations, function(labwareObject) { + return labwareObject?.attributes.loadName + }) + console.log(calibrationLoadNamesMap) + const allLabwareCalibrated = every(Object.keys(labwareCount), function(a) { + return Object.keys(calibrationLoadNamesMap).includes(a) + }) return (
- + {uploadError && } - {sessionLoaded && !uploadError && } + {sessionLoaded && !uploadError && ( + + )}
) } diff --git a/app/src/components/FileInfo/styles.css b/app/src/components/FileInfo/styles.css index f51cb3ec15f..235a0c0e2bf 100644 --- a/app/src/components/FileInfo/styles.css +++ b/app/src/components/FileInfo/styles.css @@ -9,14 +9,13 @@ width: 100%; padding-top: 1.25rem; padding-bottom: 1.25rem; + border-bottom: 1px solid var(--c-light-gray); + margin-bottom: 1rem; & > p { @apply --font-body-2-dark; } - &:not(:last-of-type) { - border-bottom: 1px solid var(--c-light-gray); - } } .title { diff --git a/app/src/components/layout/SectionContentFlex.js b/app/src/components/layout/SectionContentFlex.js new file mode 100644 index 00000000000..6360254aed9 --- /dev/null +++ b/app/src/components/layout/SectionContentFlex.js @@ -0,0 +1,43 @@ +// @flow +import * as React from 'react' + +import { SPACING_1, Flex, Box, ALIGN_CENTER } from '@opentrons/components' +import type { + UseHoverTooltipTargetProps, + StyleProps, +} from '@opentrons/components' + +export type SectionContentFlexProps = {| + title: string, + children: React.Node, + icon?: React.Node, + toolTipComponent?: React.Node, + toolTipProps?: UseHoverTooltipTargetProps, + ...StyleProps, +|} + +export function SectionContentFlex({ + title, + children, + icon, + toolTipComponent, + toolTipProps, + ...styleProps +}: SectionContentFlexProps): React.Node { + return ( + + + + +

{title}

+
+ + {icon} + {toolTipComponent} + +
+ {children} +
+
+ ) +} diff --git a/app/src/components/layout/index.js b/app/src/components/layout/index.js index 16e6f8a0903..4e66de47f38 100644 --- a/app/src/components/layout/index.js +++ b/app/src/components/layout/index.js @@ -11,3 +11,4 @@ export * from './CardContentQuarter' export * from './CardContentFlex' export * from './SectionContentFull' export * from './SectionContentHalf' +export * from './SectionContentFlex' From 5b251981b351accdfb53c2d81338f56f388ddf50 Mon Sep 17 00:00:00 2001 From: laura_danielle Date: Thu, 9 Jul 2020 11:40:29 -0400 Subject: [PATCH 03/18] Add labware calibration endpoint handlers --- app/src/calibration/__tests__/reducer.test.js | 15 ++++ .../calibration/__tests__/selectors.test.js | 10 ++- app/src/calibration/api-types.js | 35 ++++++++ .../epic/fetchCalibrationStatusEpic.js | 1 - .../calibration/labware/__fixtures__/index.js | 2 + .../__fixtures__/labware-calibration.js | 57 +++++++++++++ .../labware/__tests__/actions.test.js | 67 +++++++++++++++ .../labware/__tests__/selectors.test.js | 51 ++++++++++++ app/src/calibration/labware/actions.js | 28 +++++-- .../fetchLabwareCalibrationsEpic.test.js | 35 ++++---- ...c.js => fetchAllLabwareCalibrationEpic.js} | 30 +++---- .../epic/fetchSingleLabwareCalibrationEpic.js | 43 ++++++++++ app/src/calibration/labware/epic/index.js | 8 +- app/src/calibration/labware/index.js | 7 ++ app/src/calibration/labware/selectors.js | 11 +++ app/src/calibration/labware/types.js | 81 ++++++++++++++----- app/src/calibration/reducer.js | 8 +- app/src/calibration/types.js | 7 +- app/src/epic.js | 4 +- 19 files changed, 423 insertions(+), 77 deletions(-) create mode 100644 app/src/calibration/labware/__fixtures__/index.js create mode 100644 app/src/calibration/labware/__fixtures__/labware-calibration.js create mode 100644 app/src/calibration/labware/__tests__/actions.test.js create mode 100644 app/src/calibration/labware/__tests__/selectors.test.js rename app/src/calibration/labware/epic/{fetchLabwareCalibrationsEpic.js => fetchAllLabwareCalibrationEpic.js} (71%) create mode 100644 app/src/calibration/labware/epic/fetchSingleLabwareCalibrationEpic.js diff --git a/app/src/calibration/__tests__/reducer.test.js b/app/src/calibration/__tests__/reducer.test.js index 19c3aaf663f..039285affaf 100644 --- a/app/src/calibration/__tests__/reducer.test.js +++ b/app/src/calibration/__tests__/reducer.test.js @@ -1,6 +1,8 @@ // @flow import * as Fixtures from '../__fixtures__' +import * as LabwareFixtures from '../labware/__fixtures__' +import { actions as LabwareActions } from '../labware' import * as Actions from '../actions' import { calibrationReducer } from '../reducer' @@ -16,4 +18,17 @@ describe('calibration reducer', () => { 'robot-name': { calibrationStatus: Fixtures.mockCalibrationStatus }, }) }) + it('should handle a FETCH_LABWARE_CALIBRATION_SUCCESS', () => { + const action = LabwareActions.fetchLabwareCalibrationSuccess( + 'robot-name', + [LabwareFixtures.mockLabwareCalibration], + {} + ) + + expect(calibrationReducer({}, action)).toEqual({ + 'robot-name': { + labwareCalibration: [LabwareFixtures.mockLabwareCalibration], + }, + }) + }) }) diff --git a/app/src/calibration/__tests__/selectors.test.js b/app/src/calibration/__tests__/selectors.test.js index 4f057e462d4..5a314717667 100644 --- a/app/src/calibration/__tests__/selectors.test.js +++ b/app/src/calibration/__tests__/selectors.test.js @@ -15,7 +15,10 @@ describe('calibration selectors', () => { it('should return status if in state', () => { const state: $Shape = { calibration: { - robotName: { calibrationStatus: Fixtures.mockCalibrationStatus }, + robotName: { + calibrationStatus: Fixtures.mockCalibrationStatus, + labwareCalibration: null, + }, }, } expect(Selectors.getCalibrationStatus(state, 'robotName')).toEqual( @@ -33,7 +36,10 @@ describe('calibration selectors', () => { it('should return status if in state', () => { const state: $Shape = { calibration: { - robotName: { calibrationStatus: Fixtures.mockCalibrationStatus }, + robotName: { + calibrationStatus: Fixtures.mockCalibrationStatus, + labwareCalibration: null, + }, }, } expect(Selectors.getDeckCalibrationStatus(state, 'robotName')).toEqual( diff --git a/app/src/calibration/api-types.js b/app/src/calibration/api-types.js index d106097db2f..f26b1ff4dc7 100644 --- a/app/src/calibration/api-types.js +++ b/app/src/calibration/api-types.js @@ -34,3 +34,38 @@ export type CalibrationStatus = {| |}, |}, |} + +export type OffsetData = {| + value: Array, + lastModified: string, +|} + +export type TipLengthData = {| + value: number, + lastModified: string, +|} + +export type CalibrationData = {| + offset: OffsetData, + tipLength: ?TipLengthData, +|} + +export type SingleLabwareCalibration = {| + calibrationData: CalibrationData, + loadName: string, + namespace: string, + version: number, + parent: string, +|} + +export type LabwareCalibrationObjects = {| + attributes: SingleLabwareCalibration, + type: string, + id: string, +|} + +export type AllLabwareCalibrations = {| + data: Array, + type: string, + meta: Object, +|} diff --git a/app/src/calibration/epic/fetchCalibrationStatusEpic.js b/app/src/calibration/epic/fetchCalibrationStatusEpic.js index 49dd3027d0d..b117c99f6e9 100644 --- a/app/src/calibration/epic/fetchCalibrationStatusEpic.js +++ b/app/src/calibration/epic/fetchCalibrationStatusEpic.js @@ -24,7 +24,6 @@ const mapResponseToAction: ResponseToActionMapper ) => { const { host, body, ...responseMeta } = response const meta = { ...originalAction.meta, response: responseMeta } - return response.ok ? Actions.fetchCalibrationStatusSuccess(host.name, body, meta) : Actions.fetchCalibrationStatusFailure(host.name, body, meta) diff --git a/app/src/calibration/labware/__fixtures__/index.js b/app/src/calibration/labware/__fixtures__/index.js new file mode 100644 index 00000000000..60de771de32 --- /dev/null +++ b/app/src/calibration/labware/__fixtures__/index.js @@ -0,0 +1,2 @@ +// @flow +export * from './labware-calibration' diff --git a/app/src/calibration/labware/__fixtures__/labware-calibration.js b/app/src/calibration/labware/__fixtures__/labware-calibration.js new file mode 100644 index 00000000000..b49b888f3d6 --- /dev/null +++ b/app/src/calibration/labware/__fixtures__/labware-calibration.js @@ -0,0 +1,57 @@ +// @flow +import { GET } from '../../../robot-api' +import { + makeResponseFixtures, + mockFailureBody, +} from '../../../robot-api/__fixtures__' +import { LABWARE_CALIBRATION_PATH } from '../constants' + +import type { ResponseFixtures } from '../../../robot-api/__fixtures__' +import type { + LabwareCalibrationObjects, + AllLabwareCalibrations, +} from '../../api-types' + +export const mockLabwareCalibration: LabwareCalibrationObjects = { + attributes: { + calibrationData: { + offset: { + value: [0.0, 0.0, 0.0], + lastModified: '2020-03-27 19:43:24.642318', + }, + tipLength: { + value: 30, + lastModified: '2020-03-27 19:43:24.642318', + }, + }, + loadName: 'opentrons_96_tiprack_10ul', + namespace: 'opentrons', + version: 1, + parent: 'fake_id', + }, + id: 'some id', + type: 'Labware Calibration', +} + +export const mockAllLabwareCalibraton: AllLabwareCalibrations = { + data: [mockLabwareCalibration], + meta: {}, + type: 'Labware Calibration', +} + +export const { + successMeta: mockFetchLabwareCalibrationSuccessMeta, + failureMeta: mockFetchLabwareCalibrationFailureMeta, + success: mockFetchLabwareCalibrationSuccess, + failure: mockFetchLabwareCalibrationFailure, +}: ResponseFixtures< + Array, + {| message: string |} +> = makeResponseFixtures({ + method: GET, + path: LABWARE_CALIBRATION_PATH, + successStatus: 200, + successBody: [mockLabwareCalibration], + failureStatus: 500, + failureBody: mockFailureBody, +}) diff --git a/app/src/calibration/labware/__tests__/actions.test.js b/app/src/calibration/labware/__tests__/actions.test.js new file mode 100644 index 00000000000..a3668f6d6a5 --- /dev/null +++ b/app/src/calibration/labware/__tests__/actions.test.js @@ -0,0 +1,67 @@ +// @flow + +import * as Fixtures from '../__fixtures__' +import * as Actions from '../actions' +import type { LawareCalibrationAction } from '../types' + +type ActionSpec = {| + should: string, + creator: (...Array) => LawareCalibrationAction, + args: Array, + expected: LawareCalibrationAction, +|} + +const SPECS: Array = [ + { + should: 'create a fetchCalibrationStatus action', + creator: Actions.fetchAllLabwareCalibrations, + args: ['robot-name'], + expected: { + type: 'calibration:FETCH_ALL_LABWARE_CALIBRATIONS', + payload: { robotName: 'robot-name' }, + meta: {}, + }, + }, + { + should: 'create a fetchCalibrationStatusSuccess action', + creator: Actions.fetchLabwareCalibrationSuccess, + args: [ + 'robot-name', + Fixtures.mockFetchLabwareCalibrationSuccess.body, + { requestId: '123' }, + ], + expected: { + type: 'calibration:FETCH_LABWARE_CALIBRATION_SUCCESS', + payload: { + robotName: 'robot-name', + labwareCalibration: [Fixtures.mockLabwareCalibration], + }, + meta: { requestId: '123' }, + }, + }, + { + should: 'create a fetchCalibrationStatusFailure action', + creator: Actions.fetchLabwareCalibrationFailure, + args: [ + 'robot-name', + Fixtures.mockFetchLabwareCalibrationFailure.body, + { requestId: '123' }, + ], + expected: { + type: 'calibration:FETCH_LABWARE_CALIBRATION_FAILURE', + payload: { + robotName: 'robot-name', + error: Fixtures.mockFetchLabwareCalibrationFailure.body, + }, + meta: { requestId: '123' }, + }, + }, +] + +describe('calibration actions', () => { + SPECS.forEach(({ should, creator, args, expected }) => { + it(should, () => { + expect(creator(...args)).toEqual(expected) + }) + }) +}) diff --git a/app/src/calibration/labware/__tests__/selectors.test.js b/app/src/calibration/labware/__tests__/selectors.test.js new file mode 100644 index 00000000000..c0c1210afb1 --- /dev/null +++ b/app/src/calibration/labware/__tests__/selectors.test.js @@ -0,0 +1,51 @@ +// @flow + +import * as Fixtures from '../__fixtures__' +import * as Selectors from '../selectors' + +import type { State } from '../../../types' + +describe('calibration selectors', () => { + describe('getCalibrationStatus', () => { + it('should return null if no robot in state', () => { + const state: $Shape = { calibration: {} } + expect(Selectors.getListOfLabwareCalibrations(state, 'robotName')).toBe( + [] + ) + }) + + it('should return list of calibrations if in state', () => { + const state: $Shape = { + calibration: { + robotName: { + calibrationStatus: null, + labwareCalibration: [Fixtures.mockLabwareCalibration], + }, + }, + } + expect( + Selectors.getListOfLabwareCalibrations(state, 'robotName') + ).toEqual([Fixtures.mockLabwareCalibration]) + }) + }) +}) + +// describe('get single labware', () => { +// it('should return null if no robot in state', () => { +// const state: $Shape = { calibration: {} } +// expect(Selectors.getDeckCalibrationStatus(state, 'robotName')).toBe(null) +// }) + +// it('should return the single labware', () => { +// const state: $Shape = { +// calibration: { +// robotName: { calibrationStatus: Fixtures.mockCalibrationStatus }, +// }, +// labwareCalibration: {}, +// } +// expect(Selectors.getDeckCalibrationStatus(state, 'robotName')).toEqual( +// Fixtures.mockCalibrationStatus.deckCalibration.status +// ) +// }) +// }) +// }) diff --git a/app/src/calibration/labware/actions.js b/app/src/calibration/labware/actions.js index 3547b83d6b9..68bf3b5f179 100644 --- a/app/src/calibration/labware/actions.js +++ b/app/src/calibration/labware/actions.js @@ -2,6 +2,7 @@ import * as Constants from './constants' import * as Types from './types' +import * as APITypes from '../types' import type { RobotApiRequestMeta, @@ -10,27 +11,38 @@ import type { export const fetchAllLabwareCalibrations = ( robotName: string -): Types.FetchCalibrationStatusAction => ({ +): Types.FetchLabwareCalibrationAction => ({ type: Constants.FETCH_ALL_LABWARE_CALIBRATIONS, payload: { robotName }, meta: {}, }) export const fetchSingleLabwareCalibration = ( - robotName: string -): Types.FetchCalibrationStatusAction => ({ + robotName: string, + calibrationId: string +): Types.FetchSingleLabwareCalibrationAction => ({ type: Constants.FETCH_SINGLE_LABWARE_CALIBRATION, - payload: { robotName }, + payload: { robotName, calibrationId }, meta: {}, }) +export const fetchSingleLabwareCalibrationSuccess = ( + robotName: string, + labwareCalibration: APITypes.LabwareCalibrationObjects, + meta: RobotApiRequestMeta +): Types.FetchSingleLabwareCalibrationSuccessAction => ({ + type: Constants.FETCH_LABWARE_CALIBRATION_SUCCESS, + payload: { robotName, labwareCalibration }, + meta, +}) + export const fetchLabwareCalibrationSuccess = ( robotName: string, - calibrationStatus: Types.CalibrationStatus, + labwareCalibration: APITypes.AllLabwareCalibrations, meta: RobotApiRequestMeta -): Types.FetchCalibrationStatusSuccessAction => ({ +): Types.FetchAllLabwareCalibrationSuccessAction => ({ type: Constants.FETCH_LABWARE_CALIBRATION_SUCCESS, - payload: { robotName, calibrationStatus }, + payload: { robotName, labwareCalibration }, meta, }) @@ -38,7 +50,7 @@ export const fetchLabwareCalibrationFailure = ( robotName: string, error: RobotApiErrorResponse, meta: RobotApiRequestMeta -): Types.FetchCalibrationStatusFailureAction => ({ +): Types.FetchLabwareCalibrationFailureAction => ({ type: Constants.FETCH_LABWARE_CALIBRATION_FAILURE, payload: { robotName, error }, meta, diff --git a/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js b/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js index 89fa9e503be..6d54052c77e 100644 --- a/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js +++ b/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js @@ -3,7 +3,7 @@ import { setupEpicTestMocks, runEpicTest, } from '../../../../robot-api/__utils__' -import * as Fixtures from '../../../__fixtures__' +import * as Fixtures from '../../__fixtures__' import * as Actions from '../../actions' import { labwareCalibrationEpic } from '..' @@ -12,15 +12,15 @@ const makeTriggerActionAllCalibrations = robotName => const makeTriggerActionSingleCalibrations = robotName => Actions.fetchSingleLabwareCalibration(robotName) -describe('fetch calibration status epic', () => { +describe('fetch labware calibration epics', () => { afterEach(() => { jest.resetAllMocks() }) - it('calls GET /calibration/status', () => { + it('calls GET /labware/calibrations', () => { const mocks = setupEpicTestMocks( makeTriggerActionAllCalibrations, - Fixtures.mockFetchCalibrationStatusSuccess + Fixtures.mockFetchLabwareCalibrationSuccess ) runEpicTest(mocks, ({ hot, expectObservable, flush }) => { @@ -33,15 +33,16 @@ describe('fetch calibration status epic', () => { expect(mocks.fetchRobotApi).toHaveBeenCalledWith(mocks.robot, { method: 'GET', - path: '/calibration/status', + path: '/labware/calibrations', + query: {}, }) }) }) - it('maps successful response to FETCH_CALIBRATION_STATUS_SUCCESS', () => { + it('maps successful response to FETCH_LABWARE_CALAIBRATION_SUCCESS', () => { const mocks = setupEpicTestMocks( - makeTriggerAction, - Fixtures.mockFetchCalibrationStatusSuccess + makeTriggerActionAllCalibrations, + Fixtures.mockFetchLabwareCalibrationSuccess ) runEpicTest(mocks, ({ hot, expectObservable }) => { @@ -50,22 +51,22 @@ describe('fetch calibration status epic', () => { const output$ = labwareCalibrationEpic(action$, state$) expectObservable(output$).toBe('--a', { - a: Actions.fetchCalibrationStatusSuccess( + a: Actions.fetchLabwareCalibrationSuccess( mocks.robot.name, - Fixtures.mockFetchCalibrationStatusSuccess.body, + Fixtures.mockFetchLabwareCalibrationSuccess.body, { ...mocks.meta, - response: Fixtures.mockFetchCalibrationStatusSuccessMeta, + response: Fixtures.mockFetchLabwareCalibrationSuccessMeta, } ), }) }) }) - it('maps failed response to FETCH_CALIBRATION_STATUS_FAILURE', () => { + it('maps failed response to FETCH_LABWARE_CALAIBRATION_FAILURE', () => { const mocks = setupEpicTestMocks( - makeTriggerAction, - Fixtures.mockFetchCalibrationStatusFailure + makeTriggerActionAllCalibrations, + Fixtures.mockFetchLabwareCalibrationFailure ) runEpicTest(mocks, ({ hot, expectObservable }) => { @@ -74,12 +75,12 @@ describe('fetch calibration status epic', () => { const output$ = labwareCalibrationEpic(action$, state$) expectObservable(output$).toBe('--a', { - a: Actions.fetchCalibrationStatusFailure( + a: Actions.fetchLabwareCalibrationFailure( mocks.robot.name, - Fixtures.mockFetchCalibrationStatusFailure.body, + Fixtures.mockFetchLabwareCalibrationFailure.body, { ...mocks.meta, - response: Fixtures.mockFetchCalibrationStatusFailureMeta, + response: Fixtures.mockFetchLabwareCalibrationFailureMeta, } ), }) diff --git a/app/src/calibration/labware/epic/fetchLabwareCalibrationsEpic.js b/app/src/calibration/labware/epic/fetchAllLabwareCalibrationEpic.js similarity index 71% rename from app/src/calibration/labware/epic/fetchLabwareCalibrationsEpic.js rename to app/src/calibration/labware/epic/fetchAllLabwareCalibrationEpic.js index e87c3f95a6c..0933ecad6c2 100644 --- a/app/src/calibration/labware/epic/fetchLabwareCalibrationsEpic.js +++ b/app/src/calibration/labware/epic/fetchAllLabwareCalibrationEpic.js @@ -1,5 +1,7 @@ // @flow import { ofType } from 'redux-observable' +import omitBy from 'lodash/omitBy' +import isEmpty from 'lodash/isEmpty' import { GET } from '../../../robot-api/constants' import { mapToRobotApiRequest } from '../../../robot-api/operators' @@ -13,10 +15,15 @@ import type { import type { Epic } from '../../../types' import type { FetchLabwareCalibrationAction } from '../types' -const mapActionToRequest: ActionToRequestMapper = action => ({ - method: GET, - path: Constants.LABWARE_CALIBRATION_PATH, -}) +const mapActionToRequest: ActionToRequestMapper = action => { + // $FlowFixMe(lc, 2020-07-08): lodash flow types do not support keys with values of different types + const queryDict = omitBy(action.payload, isEmpty) + return { + method: GET, + path: Constants.LABWARE_CALIBRATION_PATH, + query: queryDict, + } +} const mapResponseToAction: ResponseToActionMapper = ( response, @@ -24,13 +31,12 @@ const mapResponseToAction: ResponseToActionMapper ) => { const { host, body, ...responseMeta } = response const meta = { ...originalAction.meta, response: responseMeta } - return response.ok ? Actions.fetchLabwareCalibrationSuccess(host.name, body, meta) : Actions.fetchLabwareCalibrationFailure(host.name, body, meta) } -export const fetchAllLabwareCalibrationsEpic: Epic = (action$, state$) => { +export const fetchAllLabwareCalibrationEpic: Epic = (action$, state$) => { return action$.pipe( ofType(Constants.FETCH_ALL_LABWARE_CALIBRATIONS), mapToRobotApiRequest( @@ -41,15 +47,3 @@ export const fetchAllLabwareCalibrationsEpic: Epic = (action$, state$) => { ) ) } - -export const fetchSingleLabwareCalibrationEpic: Epic = (action$, state$) => { - return action$.pipe( - ofType(Constants.FETCH_SINGLE_LABWARE_CALIBRATION), - mapToRobotApiRequest( - state$, - a => a.payload.robotName, - mapActionToRequest, - mapResponseToAction - ) - ) -} diff --git a/app/src/calibration/labware/epic/fetchSingleLabwareCalibrationEpic.js b/app/src/calibration/labware/epic/fetchSingleLabwareCalibrationEpic.js new file mode 100644 index 00000000000..124ac287da8 --- /dev/null +++ b/app/src/calibration/labware/epic/fetchSingleLabwareCalibrationEpic.js @@ -0,0 +1,43 @@ +// @flow +import { ofType } from 'redux-observable' + +import { GET } from '../../../robot-api/constants' +import { mapToRobotApiRequest } from '../../../robot-api/operators' +import * as Actions from '../actions' +import * as Constants from '../constants' + +import type { + ActionToRequestMapper, + ResponseToActionMapper, +} from '../../../robot-api/operators' +import type { Epic } from '../../../types' +import type { FetchSingleLabwareCalibrationAction } from '../types' + +const mapActionToRequest: ActionToRequestMapper = action => ({ + method: GET, + path: `${Constants.LABWARE_CALIBRATION_PATH}/${action.payload.calibrationId}`, +}) + +const mapResponseToAction: ResponseToActionMapper = ( + response, + originalAction +) => { + const { host, body, ...responseMeta } = response + const meta = { ...originalAction.meta, response: responseMeta } + + return response.ok + ? Actions.fetchSingleLabwareCalibrationSuccess(host.name, body, meta) + : Actions.fetchLabwareCalibrationFailure(host.name, body, meta) +} + +export const fetchSingleLabwareCalibrationEpic: Epic = (action$, state$) => { + return action$.pipe( + ofType(Constants.FETCH_SINGLE_LABWARE_CALIBRATION), + mapToRobotApiRequest( + state$, + a => a.payload.robotName, + mapActionToRequest, + mapResponseToAction + ) + ) +} diff --git a/app/src/calibration/labware/epic/index.js b/app/src/calibration/labware/epic/index.js index 45ae7702744..8da9e1f7193 100644 --- a/app/src/calibration/labware/epic/index.js +++ b/app/src/calibration/labware/epic/index.js @@ -1,14 +1,12 @@ // @flow import { combineEpics } from 'redux-observable' -import { - fetchAllLabwareCalibrationsEpic, - fetchSingleLabwareCalibrationEpic, -} from './fetchLabwareCalibrationsEpic' +import { fetchAllLabwareCalibrationEpic } from './fetchAllLabwareCalibrationEpic' +import { fetchSingleLabwareCalibrationEpic } from './fetchSingleLabwareCalibrationEpic' import type { Epic } from '../../../types' export const labwareCalibrationEpic: Epic = combineEpics( - fetchAllLabwareCalibrationsEpic, + fetchAllLabwareCalibrationEpic, fetchSingleLabwareCalibrationEpic ) diff --git a/app/src/calibration/labware/index.js b/app/src/calibration/labware/index.js index e69de29bb2d..34eed93acf2 100644 --- a/app/src/calibration/labware/index.js +++ b/app/src/calibration/labware/index.js @@ -0,0 +1,7 @@ +// @flow +// labware calibration actions, selectors and constants +import * as actions from './actions' +import * as constants from './constants' +import * as selectors from './selectors' + +export { constants, selectors, actions } diff --git a/app/src/calibration/labware/selectors.js b/app/src/calibration/labware/selectors.js index e69de29bb2d..d0c8898f60a 100644 --- a/app/src/calibration/labware/selectors.js +++ b/app/src/calibration/labware/selectors.js @@ -0,0 +1,11 @@ +// @flow + +import type { State } from '../../types' +import type { LabwareCalibrationObjects } from './../types' + +export const getListOfLabwareCalibrations = ( + state: State, + robotName: string +): Array | null => { + return state.calibration[robotName]?.labwareCalibration?.data ?? null +} diff --git a/app/src/calibration/labware/types.js b/app/src/calibration/labware/types.js index 7d2bf569cac..0a569946c76 100644 --- a/app/src/calibration/labware/types.js +++ b/app/src/calibration/labware/types.js @@ -1,24 +1,69 @@ // @flow import type { - RobotApiRequestMeta, - RobotApiErrorResponse, - } from '../robot-api/types' - - import type { CalibrationStatus } from './types' - - import typeof { - FETCH_CALIBRATION_STATUS, - FETCH_CALIBRATION_STATUS_SUCCESS, - FETCH_CALIBRATION_STATUS_FAILURE, - } from './constants' - - export * from './api-types' + RobotApiRequestMeta, + RobotApiErrorResponse, +} from '../../robot-api/types' +import typeof { + FETCH_ALL_LABWARE_CALIBRATIONS, + FETCH_SINGLE_LABWARE_CALIBRATION, + FETCH_LABWARE_CALIBRATION_SUCCESS, + FETCH_LABWARE_CALIBRATION_FAILURE, +} from './constants' + +import type { + AllLabwareCalibrations, + LabwareCalibrationObjects, +} from './../api-types' export type FetchLabwareCalibrationAction = {| - type: FETCH_CALIBRATION_STATUS, - payload: {| robotName: string |}, - meta: RobotApiRequestMeta, - |} - \ No newline at end of file + type: FETCH_ALL_LABWARE_CALIBRATIONS, + payload: {| + robotName: string, + loadName?: string, + version?: number, + namespace?: string, + |}, + meta: RobotApiRequestMeta, +|} + +export type FetchSingleLabwareCalibrationAction = {| + type: FETCH_SINGLE_LABWARE_CALIBRATION, + payload: {| + robotName: string, + calibrationId: string, + |}, + meta: RobotApiRequestMeta, +|} + +export type FetchAllLabwareCalibrationSuccessAction = {| + type: FETCH_LABWARE_CALIBRATION_SUCCESS, + payload: {| + robotName: string, + labwareCalibration: AllLabwareCalibrations, + |}, + meta: RobotApiRequestMeta, +|} + +export type FetchSingleLabwareCalibrationSuccessAction = {| + type: FETCH_LABWARE_CALIBRATION_SUCCESS, + payload: {| + robotName: string, + labwareCalibration: LabwareCalibrationObjects, + |}, + meta: RobotApiRequestMeta, +|} + +export type FetchLabwareCalibrationFailureAction = {| + type: FETCH_LABWARE_CALIBRATION_FAILURE, + payload: {| robotName: string, error: RobotApiErrorResponse |}, + meta: RobotApiRequestMeta, +|} + +export type LawareCalibrationAction = + | FetchLabwareCalibrationAction + | FetchSingleLabwareCalibrationAction + | FetchAllLabwareCalibrationSuccessAction + | FetchSingleLabwareCalibrationSuccessAction + | FetchLabwareCalibrationFailureAction diff --git a/app/src/calibration/reducer.js b/app/src/calibration/reducer.js index 4e0062e6388..cdfc9975e9b 100644 --- a/app/src/calibration/reducer.js +++ b/app/src/calibration/reducer.js @@ -1,6 +1,6 @@ // @flow import * as Constants from './constants' -import * as LabwareConstants from './labware/constants' +import { constants as LabwareConstants } from './labware' import type { Action } from '../types' import type { CalibrationState } from './types' @@ -23,17 +23,15 @@ export function calibrationReducer( } } case LabwareConstants.FETCH_LABWARE_CALIBRATION_SUCCESS: { - const { robotName, labwareCalibrations } = action.payload + const { robotName, labwareCalibration } = action.payload return { ...state, [robotName]: { ...(state[robotName] ?? {}), - labwareCalibrations, + labwareCalibration, }, } } - // create new action type of labware calibration - // don't follow exactly above because it would delete cal status. } return state } diff --git a/app/src/calibration/types.js b/app/src/calibration/types.js index 5a38371c28e..95e32d5b37a 100644 --- a/app/src/calibration/types.js +++ b/app/src/calibration/types.js @@ -5,7 +5,9 @@ import type { RobotApiErrorResponse, } from '../robot-api/types' -import type { CalibrationStatus } from './types' +import type { CalibrationStatus, AllLabwareCalibrations } from './types' + +import type { LawareCalibrationAction } from './labware/types' import typeof { FETCH_CALIBRATION_STATUS, @@ -40,10 +42,11 @@ export type CalibrationAction = | FetchCalibrationStatusAction | FetchCalibrationStatusSuccessAction | FetchCalibrationStatusFailureAction + | LawareCalibrationAction export type PerRobotCalibrationState = $ReadOnly<{| calibrationStatus: CalibrationStatus, - // add stuff here + labwareCalibration: ?AllLabwareCalibrations, |}> export type CalibrationState = $ReadOnly<{ diff --git a/app/src/epic.js b/app/src/epic.js index f94197589d5..0a42204e706 100644 --- a/app/src/epic.js +++ b/app/src/epic.js @@ -17,6 +17,7 @@ import { alertsEpic } from './alerts/epic' import { systemInfoEpic } from './system-info/epic' import { sessionsEpic } from './sessions/epic' import { calibrationEpic } from './calibration/epic' +import { labwareCalibrationEpic } from './calibration/labware/epic' import type { Epic } from './types' @@ -35,5 +36,6 @@ export const rootEpic: Epic = combineEpics( alertsEpic, systemInfoEpic, sessionsEpic, - calibrationEpic + calibrationEpic, + labwareCalibrationEpic ) From 5add224b7c9a100bd3e7bd859c63b71a30da36f0 Mon Sep 17 00:00:00 2001 From: laura_danielle Date: Thu, 9 Jul 2020 15:06:44 -0400 Subject: [PATCH 04/18] Fixup tests and flow --- app/src/calibration/__tests__/reducer.test.js | 4 +- .../__fixtures__/labware-calibration.js | 4 +- .../labware/__tests__/actions.test.js | 2 +- .../labware/__tests__/selectors.test.js | 29 ++------- app/src/calibration/labware/actions.js | 19 ------ app/src/calibration/labware/constants.js | 2 - .../fetchLabwareCalibrationsEpic.test.js | 2 - .../epic/fetchAllLabwareCalibrationEpic.js | 7 ++- .../epic/fetchSingleLabwareCalibrationEpic.js | 43 ------------- app/src/calibration/labware/epic/index.js | 4 +- app/src/calibration/labware/types.js | 26 +------- .../FileInfo/ProtocolLabwareList.js | 9 ++- .../FileInfo/__tests__/continue.test.js | 63 +++++++++++++++++-- .../__tests__/protocollabwarelist.test.js | 54 ---------------- app/src/components/FileInfo/index.js | 2 +- app/src/components/FileInfo/styles.css | 6 ++ .../components/layout/SectionContentFlex.js | 34 +++++----- 17 files changed, 107 insertions(+), 203 deletions(-) delete mode 100644 app/src/calibration/labware/epic/fetchSingleLabwareCalibrationEpic.js delete mode 100644 app/src/components/FileInfo/__tests__/protocollabwarelist.test.js diff --git a/app/src/calibration/__tests__/reducer.test.js b/app/src/calibration/__tests__/reducer.test.js index 039285affaf..ecfbc20af54 100644 --- a/app/src/calibration/__tests__/reducer.test.js +++ b/app/src/calibration/__tests__/reducer.test.js @@ -21,13 +21,13 @@ describe('calibration reducer', () => { it('should handle a FETCH_LABWARE_CALIBRATION_SUCCESS', () => { const action = LabwareActions.fetchLabwareCalibrationSuccess( 'robot-name', - [LabwareFixtures.mockLabwareCalibration], + LabwareFixtures.mockAllLabwareCalibraton, {} ) expect(calibrationReducer({}, action)).toEqual({ 'robot-name': { - labwareCalibration: [LabwareFixtures.mockLabwareCalibration], + labwareCalibration: LabwareFixtures.mockAllLabwareCalibraton, }, }) }) diff --git a/app/src/calibration/labware/__fixtures__/labware-calibration.js b/app/src/calibration/labware/__fixtures__/labware-calibration.js index b49b888f3d6..d3225d926fe 100644 --- a/app/src/calibration/labware/__fixtures__/labware-calibration.js +++ b/app/src/calibration/labware/__fixtures__/labware-calibration.js @@ -45,13 +45,13 @@ export const { success: mockFetchLabwareCalibrationSuccess, failure: mockFetchLabwareCalibrationFailure, }: ResponseFixtures< - Array, + AllLabwareCalibrations, {| message: string |} > = makeResponseFixtures({ method: GET, path: LABWARE_CALIBRATION_PATH, successStatus: 200, - successBody: [mockLabwareCalibration], + successBody: mockAllLabwareCalibraton, failureStatus: 500, failureBody: mockFailureBody, }) diff --git a/app/src/calibration/labware/__tests__/actions.test.js b/app/src/calibration/labware/__tests__/actions.test.js index a3668f6d6a5..bc8294181ab 100644 --- a/app/src/calibration/labware/__tests__/actions.test.js +++ b/app/src/calibration/labware/__tests__/actions.test.js @@ -34,7 +34,7 @@ const SPECS: Array = [ type: 'calibration:FETCH_LABWARE_CALIBRATION_SUCCESS', payload: { robotName: 'robot-name', - labwareCalibration: [Fixtures.mockLabwareCalibration], + labwareCalibration: Fixtures.mockAllLabwareCalibraton, }, meta: { requestId: '123' }, }, diff --git a/app/src/calibration/labware/__tests__/selectors.test.js b/app/src/calibration/labware/__tests__/selectors.test.js index c0c1210afb1..c88c93cd1c3 100644 --- a/app/src/calibration/labware/__tests__/selectors.test.js +++ b/app/src/calibration/labware/__tests__/selectors.test.js @@ -1,6 +1,7 @@ // @flow import * as Fixtures from '../__fixtures__' +import * as StatusFixtures from '../../__fixtures__' import * as Selectors from '../selectors' import type { State } from '../../../types' @@ -10,7 +11,7 @@ describe('calibration selectors', () => { it('should return null if no robot in state', () => { const state: $Shape = { calibration: {} } expect(Selectors.getListOfLabwareCalibrations(state, 'robotName')).toBe( - [] + null ) }) @@ -18,34 +19,14 @@ describe('calibration selectors', () => { const state: $Shape = { calibration: { robotName: { - calibrationStatus: null, - labwareCalibration: [Fixtures.mockLabwareCalibration], + calibrationStatus: StatusFixtures.mockCalibrationStatus, + labwareCalibration: Fixtures.mockAllLabwareCalibraton, }, }, } expect( Selectors.getListOfLabwareCalibrations(state, 'robotName') - ).toEqual([Fixtures.mockLabwareCalibration]) + ).toEqual(Fixtures.mockAllLabwareCalibraton.data) }) }) }) - -// describe('get single labware', () => { -// it('should return null if no robot in state', () => { -// const state: $Shape = { calibration: {} } -// expect(Selectors.getDeckCalibrationStatus(state, 'robotName')).toBe(null) -// }) - -// it('should return the single labware', () => { -// const state: $Shape = { -// calibration: { -// robotName: { calibrationStatus: Fixtures.mockCalibrationStatus }, -// }, -// labwareCalibration: {}, -// } -// expect(Selectors.getDeckCalibrationStatus(state, 'robotName')).toEqual( -// Fixtures.mockCalibrationStatus.deckCalibration.status -// ) -// }) -// }) -// }) diff --git a/app/src/calibration/labware/actions.js b/app/src/calibration/labware/actions.js index 68bf3b5f179..a87696253b5 100644 --- a/app/src/calibration/labware/actions.js +++ b/app/src/calibration/labware/actions.js @@ -17,25 +17,6 @@ export const fetchAllLabwareCalibrations = ( meta: {}, }) -export const fetchSingleLabwareCalibration = ( - robotName: string, - calibrationId: string -): Types.FetchSingleLabwareCalibrationAction => ({ - type: Constants.FETCH_SINGLE_LABWARE_CALIBRATION, - payload: { robotName, calibrationId }, - meta: {}, -}) - -export const fetchSingleLabwareCalibrationSuccess = ( - robotName: string, - labwareCalibration: APITypes.LabwareCalibrationObjects, - meta: RobotApiRequestMeta -): Types.FetchSingleLabwareCalibrationSuccessAction => ({ - type: Constants.FETCH_LABWARE_CALIBRATION_SUCCESS, - payload: { robotName, labwareCalibration }, - meta, -}) - export const fetchLabwareCalibrationSuccess = ( robotName: string, labwareCalibration: APITypes.AllLabwareCalibrations, diff --git a/app/src/calibration/labware/constants.js b/app/src/calibration/labware/constants.js index ee484081bb2..7034a15350d 100644 --- a/app/src/calibration/labware/constants.js +++ b/app/src/calibration/labware/constants.js @@ -5,8 +5,6 @@ export const LABWARE_CALIBRATION_PATH: '/labware/calibrations' = export const FETCH_ALL_LABWARE_CALIBRATIONS: 'calibration:FETCH_ALL_LABWARE_CALIBRATIONS' = 'calibration:FETCH_ALL_LABWARE_CALIBRATIONS' -export const FETCH_SINGLE_LABWARE_CALIBRATION: 'calibration:FETCH_SINGLE_LABWARE_CALIBRATION' = - 'calibration:FETCH_SINGLE_LABWARE_CALIBRATION' export const FETCH_LABWARE_CALIBRATION_SUCCESS: 'calibration:FETCH_LABWARE_CALIBRATION_SUCCESS' = 'calibration:FETCH_LABWARE_CALIBRATION_SUCCESS' diff --git a/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js b/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js index 6d54052c77e..25899f4c575 100644 --- a/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js +++ b/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js @@ -9,8 +9,6 @@ import { labwareCalibrationEpic } from '..' const makeTriggerActionAllCalibrations = robotName => Actions.fetchAllLabwareCalibrations(robotName) -const makeTriggerActionSingleCalibrations = robotName => - Actions.fetchSingleLabwareCalibration(robotName) describe('fetch labware calibration epics', () => { afterEach(() => { diff --git a/app/src/calibration/labware/epic/fetchAllLabwareCalibrationEpic.js b/app/src/calibration/labware/epic/fetchAllLabwareCalibrationEpic.js index 0933ecad6c2..e1eb7fa0bba 100644 --- a/app/src/calibration/labware/epic/fetchAllLabwareCalibrationEpic.js +++ b/app/src/calibration/labware/epic/fetchAllLabwareCalibrationEpic.js @@ -17,7 +17,12 @@ import type { FetchLabwareCalibrationAction } from '../types' const mapActionToRequest: ActionToRequestMapper = action => { // $FlowFixMe(lc, 2020-07-08): lodash flow types do not support keys with values of different types - const queryDict = omitBy(action.payload, isEmpty) + const payloadWithoutRobot = omitBy(action.payload, function(value, key) { + return key === 'robotName' + }) + // $FlowFixMe(lc, 2020-07-08): lodash flow types do not support keys with values of different types + const queryDict = omitBy(payloadWithoutRobot, isEmpty) + return { method: GET, path: Constants.LABWARE_CALIBRATION_PATH, diff --git a/app/src/calibration/labware/epic/fetchSingleLabwareCalibrationEpic.js b/app/src/calibration/labware/epic/fetchSingleLabwareCalibrationEpic.js deleted file mode 100644 index 124ac287da8..00000000000 --- a/app/src/calibration/labware/epic/fetchSingleLabwareCalibrationEpic.js +++ /dev/null @@ -1,43 +0,0 @@ -// @flow -import { ofType } from 'redux-observable' - -import { GET } from '../../../robot-api/constants' -import { mapToRobotApiRequest } from '../../../robot-api/operators' -import * as Actions from '../actions' -import * as Constants from '../constants' - -import type { - ActionToRequestMapper, - ResponseToActionMapper, -} from '../../../robot-api/operators' -import type { Epic } from '../../../types' -import type { FetchSingleLabwareCalibrationAction } from '../types' - -const mapActionToRequest: ActionToRequestMapper = action => ({ - method: GET, - path: `${Constants.LABWARE_CALIBRATION_PATH}/${action.payload.calibrationId}`, -}) - -const mapResponseToAction: ResponseToActionMapper = ( - response, - originalAction -) => { - const { host, body, ...responseMeta } = response - const meta = { ...originalAction.meta, response: responseMeta } - - return response.ok - ? Actions.fetchSingleLabwareCalibrationSuccess(host.name, body, meta) - : Actions.fetchLabwareCalibrationFailure(host.name, body, meta) -} - -export const fetchSingleLabwareCalibrationEpic: Epic = (action$, state$) => { - return action$.pipe( - ofType(Constants.FETCH_SINGLE_LABWARE_CALIBRATION), - mapToRobotApiRequest( - state$, - a => a.payload.robotName, - mapActionToRequest, - mapResponseToAction - ) - ) -} diff --git a/app/src/calibration/labware/epic/index.js b/app/src/calibration/labware/epic/index.js index 8da9e1f7193..9951e6ef2ac 100644 --- a/app/src/calibration/labware/epic/index.js +++ b/app/src/calibration/labware/epic/index.js @@ -2,11 +2,9 @@ import { combineEpics } from 'redux-observable' import { fetchAllLabwareCalibrationEpic } from './fetchAllLabwareCalibrationEpic' -import { fetchSingleLabwareCalibrationEpic } from './fetchSingleLabwareCalibrationEpic' import type { Epic } from '../../../types' export const labwareCalibrationEpic: Epic = combineEpics( - fetchAllLabwareCalibrationEpic, - fetchSingleLabwareCalibrationEpic + fetchAllLabwareCalibrationEpic ) diff --git a/app/src/calibration/labware/types.js b/app/src/calibration/labware/types.js index 0a569946c76..f98cfa338ac 100644 --- a/app/src/calibration/labware/types.js +++ b/app/src/calibration/labware/types.js @@ -7,15 +7,11 @@ import type { import typeof { FETCH_ALL_LABWARE_CALIBRATIONS, - FETCH_SINGLE_LABWARE_CALIBRATION, FETCH_LABWARE_CALIBRATION_SUCCESS, FETCH_LABWARE_CALIBRATION_FAILURE, } from './constants' -import type { - AllLabwareCalibrations, - LabwareCalibrationObjects, -} from './../api-types' +import type { AllLabwareCalibrations } from './../api-types' export type FetchLabwareCalibrationAction = {| type: FETCH_ALL_LABWARE_CALIBRATIONS, @@ -28,15 +24,6 @@ export type FetchLabwareCalibrationAction = {| meta: RobotApiRequestMeta, |} -export type FetchSingleLabwareCalibrationAction = {| - type: FETCH_SINGLE_LABWARE_CALIBRATION, - payload: {| - robotName: string, - calibrationId: string, - |}, - meta: RobotApiRequestMeta, -|} - export type FetchAllLabwareCalibrationSuccessAction = {| type: FETCH_LABWARE_CALIBRATION_SUCCESS, payload: {| @@ -46,15 +33,6 @@ export type FetchAllLabwareCalibrationSuccessAction = {| meta: RobotApiRequestMeta, |} -export type FetchSingleLabwareCalibrationSuccessAction = {| - type: FETCH_LABWARE_CALIBRATION_SUCCESS, - payload: {| - robotName: string, - labwareCalibration: LabwareCalibrationObjects, - |}, - meta: RobotApiRequestMeta, -|} - export type FetchLabwareCalibrationFailureAction = {| type: FETCH_LABWARE_CALIBRATION_FAILURE, payload: {| robotName: string, error: RobotApiErrorResponse |}, @@ -63,7 +41,5 @@ export type FetchLabwareCalibrationFailureAction = {| export type LawareCalibrationAction = | FetchLabwareCalibrationAction - | FetchSingleLabwareCalibrationAction | FetchAllLabwareCalibrationSuccessAction - | FetchSingleLabwareCalibrationSuccessAction | FetchLabwareCalibrationFailureAction diff --git a/app/src/components/FileInfo/ProtocolLabwareList.js b/app/src/components/FileInfo/ProtocolLabwareList.js index 09a034625ba..6039b5c3c27 100644 --- a/app/src/components/FileInfo/ProtocolLabwareList.js +++ b/app/src/components/FileInfo/ProtocolLabwareList.js @@ -33,12 +33,15 @@ export function ProtocolLabwareList({ const [targetProps, tooltipProps] = useHoverTooltip({ placement: TOOLTIP_AUTO, }) - const iconComponent = + const iconComponent = ( + + ) const toolTipComponent = ( {'calibrated offset from labware origin point'} ) + return ( - - {calibration} +
+ {calibration}
diff --git a/app/src/components/FileInfo/__tests__/continue.test.js b/app/src/components/FileInfo/__tests__/continue.test.js index 40b48749a6c..e5d46fa0db0 100644 --- a/app/src/components/FileInfo/__tests__/continue.test.js +++ b/app/src/components/FileInfo/__tests__/continue.test.js @@ -1,26 +1,77 @@ // @flow import * as React from 'react' +import { Provider } from 'react-redux' import { mount } from 'enzyme' +import { getCalibrateLocation, getRunLocation } from '../../../nav' import { PrimaryButton } from '@opentrons/components' import { Continue } from '../Continue' +import type { State } from '../../../types' + +jest.mock('../../nav') + +const mockGetCalNavigation: JestMockFn< + [State], + $Call +> = getCalibrateLocation + +const mockGetRunNavigation: JestMockFn< + [State], + $Call +> = getRunLocation + +const mockRunPath = '/path/to/run' +const mockCalPath = '/path/to/cal' + describe('Continue to run or calibration button component', () => { - const render = (status: boolean = false) => { - return mount() + let mockStore + let render + + const CALIBRATE_SELECTOR = { + id: 'calibrate', + path: mockCalPath, + title: 'CALIBRATE', + iconName: 'ot-calibrate', + disabledReason: null, } + const RUN_SELECTOR = { + id: 'run', + path: mockRunPath, + title: 'RUN', + iconName: 'ot-run', + disabledReason: null, + } + + beforeEach(() => { + mockGetCalNavigation.mockReturnValue(CALIBRATE_SELECTOR) + mockGetRunNavigation.mockReturnValue(RUN_SELECTOR) + + mockStore = { + subscribe: () => {}, + getState: () => ({ state: true }), + dispatch: jest.fn(), + } + render = (labwareCalibrated: boolean = false) => + mount( + + + + ) + }) + it('Default button renders to continue to labware when not all labware is calibrated', () => { const wrapper = render() const button = wrapper.find(PrimaryButton) - console.log(button) - expect(button.children).toEqual('Proceed to Calibrate') + expect(wrapper.children).toEqual('Proceed to Calibrate') + expect(button.props.path).toEqual(mockCalPath) }) it('renders nothing when calibration is OK', () => { const wrapper = render(true) const button = wrapper.find(PrimaryButton) - console.log(button.children) - expect(button.children).toEqual('Proceed to Calibrate') + expect(wrapper.children).toEqual('Proceed to Run') + expect(button.props.path).toEqual(mockRunPath) }) }) diff --git a/app/src/components/FileInfo/__tests__/protocollabwarelist.test.js b/app/src/components/FileInfo/__tests__/protocollabwarelist.test.js deleted file mode 100644 index 58ae8fd179f..00000000000 --- a/app/src/components/FileInfo/__tests__/protocollabwarelist.test.js +++ /dev/null @@ -1,54 +0,0 @@ -// @flow -import * as React from 'react' -import { mount } from 'enzyme' - -import { Flex, Icon } from '@opentrons/components' -import { ProtocolLabwareList } from '../ProtocolLabwareList' - -import { SectionContentFlex } from '../../layout' - -describe('Protocol Labware List Component', () => { - const render = ( - labware: Array, - quantity: Array, - calibration: Array - ) => { - return mount( - - ) - } - - it('renders nothing when no labware exists in the protocol', () => { - const wrapper = render([], [], []) - expect(wrapper).toEqual({}) - }) - - it('renders three columns when a labware exists', () => { - const rowToRender = ( - - Not yet calibrated - - ) - const wrapper = render(['opentrons_labware'], ['x1'], [rowToRender]) - const flex = wrapper.find(Flex) - const sections = flex.find(SectionContentFlex) - console.log(sections) - expect(wrapper).toEqual({}) - }) - - it('renders a table with values when given calibration data', () => { - const rowToRender = Not yet calibrated - // const wrapper = render(['opentrons_labware'], ['x1'], [rowToRender]) - // const parent = wrapper.find(Flex).first() - // const icon = wrapper.find(Icon) - - // expect(parent.prop('color')).toBe(COLOR_WARNING) - // expect(icon.prop('name')).toEqual('alert-circle') - // expect(wrapper.html()).toMatch(/not yet been calibrated/i) - // expect(wrapper.html()).toMatch(/please perform a deck calibration/i) - }) -}) diff --git a/app/src/components/FileInfo/index.js b/app/src/components/FileInfo/index.js index f98c76d94dc..a5add26e291 100644 --- a/app/src/components/FileInfo/index.js +++ b/app/src/components/FileInfo/index.js @@ -65,7 +65,7 @@ export function FileInfo(props: FileInfoProps): React.Node { const calibrationLoadNamesMap = keyBy(calibrations, function(labwareObject) { return labwareObject?.attributes.loadName }) - console.log(calibrationLoadNamesMap) + const allLabwareCalibrated = every(Object.keys(labwareCount), function(a) { return Object.keys(calibrationLoadNamesMap).includes(a) }) diff --git a/app/src/components/FileInfo/styles.css b/app/src/components/FileInfo/styles.css index 235a0c0e2bf..85ef0f0ad35 100644 --- a/app/src/components/FileInfo/styles.css +++ b/app/src/components/FileInfo/styles.css @@ -115,6 +115,12 @@ margin: 0.5rem 0.5rem 0.5rem 0; } +.calibration_data_info_icon { + height: 1rem; + width: 1rem; + fill: var(--c-med-gray); + } + .warning_info_wrapper { display: flex; align-items: center; diff --git a/app/src/components/layout/SectionContentFlex.js b/app/src/components/layout/SectionContentFlex.js index 6360254aed9..1774ba13976 100644 --- a/app/src/components/layout/SectionContentFlex.js +++ b/app/src/components/layout/SectionContentFlex.js @@ -1,7 +1,13 @@ // @flow import * as React from 'react' -import { SPACING_1, Flex, Box, ALIGN_CENTER } from '@opentrons/components' +import { + SPACING_1, + Flex, + Box, + ALIGN_CENTER, + FLEX_AUTO, +} from '@opentrons/components' import type { UseHoverTooltipTargetProps, StyleProps, @@ -25,19 +31,17 @@ export function SectionContentFlex({ ...styleProps }: SectionContentFlexProps): React.Node { return ( - - - - -

{title}

-
- - {icon} - {toolTipComponent} - -
- {children} -
-
+ + + +

{title}

+
+ + {icon} + {toolTipComponent} + +
+ {children} +
) } From 7de46c0515d9e4fa7be65f41c0261a837c567e06 Mon Sep 17 00:00:00 2001 From: laura_danielle Date: Fri, 10 Jul 2020 15:06:49 -0400 Subject: [PATCH 05/18] Make requested changes --- app/src/calibration/api-types.js | 4 +- .../labware/__tests__/selectors.test.js | 38 +++++++++---------- .../epic/fetchAllLabwareCalibrationEpic.js | 5 +-- app/src/calibration/labware/index.js | 8 ++-- .../FileInfo/ProtocolLabwareCard.js | 2 +- .../FileInfo/ProtocolLabwareList.js | 14 ++++--- .../FileInfo/__tests__/continue.test.js | 2 +- app/src/components/FileInfo/index.js | 4 +- 8 files changed, 35 insertions(+), 42 deletions(-) diff --git a/app/src/calibration/api-types.js b/app/src/calibration/api-types.js index f26b1ff4dc7..7f7f50f25c5 100644 --- a/app/src/calibration/api-types.js +++ b/app/src/calibration/api-types.js @@ -47,7 +47,7 @@ export type TipLengthData = {| export type CalibrationData = {| offset: OffsetData, - tipLength: ?TipLengthData, + tipLength: TipLengthData | null, |} export type SingleLabwareCalibration = {| @@ -65,7 +65,7 @@ export type LabwareCalibrationObjects = {| |} export type AllLabwareCalibrations = {| - data: Array, + data: Array, type: string, meta: Object, |} diff --git a/app/src/calibration/labware/__tests__/selectors.test.js b/app/src/calibration/labware/__tests__/selectors.test.js index c88c93cd1c3..1f875bfb8a0 100644 --- a/app/src/calibration/labware/__tests__/selectors.test.js +++ b/app/src/calibration/labware/__tests__/selectors.test.js @@ -6,27 +6,25 @@ import * as Selectors from '../selectors' import type { State } from '../../../types' -describe('calibration selectors', () => { - describe('getCalibrationStatus', () => { - it('should return null if no robot in state', () => { - const state: $Shape = { calibration: {} } - expect(Selectors.getListOfLabwareCalibrations(state, 'robotName')).toBe( - null - ) - }) +describe('labware calibration selectors', () => { + it('should return null if no robot in state', () => { + const state: $Shape = { calibration: {} } + expect(Selectors.getListOfLabwareCalibrations(state, 'robotName')).toBe( + null + ) + }) - it('should return list of calibrations if in state', () => { - const state: $Shape = { - calibration: { - robotName: { - calibrationStatus: StatusFixtures.mockCalibrationStatus, - labwareCalibration: Fixtures.mockAllLabwareCalibraton, - }, + it('should return list of calibrations if in state', () => { + const state: $Shape = { + calibration: { + robotName: { + calibrationStatus: StatusFixtures.mockCalibrationStatus, + labwareCalibration: Fixtures.mockAllLabwareCalibraton, }, - } - expect( - Selectors.getListOfLabwareCalibrations(state, 'robotName') - ).toEqual(Fixtures.mockAllLabwareCalibraton.data) - }) + }, + } + expect(Selectors.getListOfLabwareCalibrations(state, 'robotName')).toEqual( + Fixtures.mockAllLabwareCalibraton.data + ) }) }) diff --git a/app/src/calibration/labware/epic/fetchAllLabwareCalibrationEpic.js b/app/src/calibration/labware/epic/fetchAllLabwareCalibrationEpic.js index e1eb7fa0bba..0a97cf08d6b 100644 --- a/app/src/calibration/labware/epic/fetchAllLabwareCalibrationEpic.js +++ b/app/src/calibration/labware/epic/fetchAllLabwareCalibrationEpic.js @@ -16,10 +16,7 @@ import type { Epic } from '../../../types' import type { FetchLabwareCalibrationAction } from '../types' const mapActionToRequest: ActionToRequestMapper = action => { - // $FlowFixMe(lc, 2020-07-08): lodash flow types do not support keys with values of different types - const payloadWithoutRobot = omitBy(action.payload, function(value, key) { - return key === 'robotName' - }) + const { robotName, ...payloadWithoutRobot } = action.payload // $FlowFixMe(lc, 2020-07-08): lodash flow types do not support keys with values of different types const queryDict = omitBy(payloadWithoutRobot, isEmpty) diff --git a/app/src/calibration/labware/index.js b/app/src/calibration/labware/index.js index 34eed93acf2..43606460a85 100644 --- a/app/src/calibration/labware/index.js +++ b/app/src/calibration/labware/index.js @@ -1,7 +1,5 @@ // @flow // labware calibration actions, selectors and constants -import * as actions from './actions' -import * as constants from './constants' -import * as selectors from './selectors' - -export { constants, selectors, actions } +export * from './actions' +export * from './constants' +export * from './selectors' diff --git a/app/src/components/FileInfo/ProtocolLabwareCard.js b/app/src/components/FileInfo/ProtocolLabwareCard.js index 8e34e20694d..ba4bdf26e91 100644 --- a/app/src/components/FileInfo/ProtocolLabwareCard.js +++ b/app/src/components/FileInfo/ProtocolLabwareCard.js @@ -55,7 +55,7 @@ export function ProtocolLabwareCard({ labware={Object.keys(labware)} quantity={labwareQuantity} calibration={labwareCalibration} - >
+ /> ) } diff --git a/app/src/components/FileInfo/ProtocolLabwareList.js b/app/src/components/FileInfo/ProtocolLabwareList.js index 6039b5c3c27..0ba16ba16d3 100644 --- a/app/src/components/FileInfo/ProtocolLabwareList.js +++ b/app/src/components/FileInfo/ProtocolLabwareList.js @@ -38,30 +38,32 @@ export function ProtocolLabwareList({ ) const toolTipComponent = ( - {'calibrated offset from labware origin point'} + calibrated offset from labware origin point ) - + const LABWARE_TYPE = "Type" + const LABWARE_QUANTITY = "Quantity" + const CALIBRATION_DATA = "Calibration Data" return ( - + {labware.map(name => ( {name} ))} - + {quantity.map((item, index) => ( {item} ))} Date: Sun, 12 Jul 2020 00:30:32 -0400 Subject: [PATCH 06/18] Make requested changes --- app/src/calibration/__tests__/reducer.test.js | 4 +-- app/src/calibration/index.js | 3 ++ .../__fixtures__/labware-calibration.js | 4 +-- app/src/calibration/labware/selectors.js | 2 +- app/src/calibration/reducer.js | 4 +-- .../FileInfo/ProtocolLabwareList.js | 32 +++++++++++-------- .../FileInfo/__tests__/continue.test.js | 16 +++++++--- app/src/components/FileInfo/index.js | 9 ++---- app/src/components/FileInfo/styles.css | 6 ---- 9 files changed, 42 insertions(+), 38 deletions(-) diff --git a/app/src/calibration/__tests__/reducer.test.js b/app/src/calibration/__tests__/reducer.test.js index ecfbc20af54..c4a0a2ff85a 100644 --- a/app/src/calibration/__tests__/reducer.test.js +++ b/app/src/calibration/__tests__/reducer.test.js @@ -2,7 +2,7 @@ import * as Fixtures from '../__fixtures__' import * as LabwareFixtures from '../labware/__fixtures__' -import { actions as LabwareActions } from '../labware' +import { labware as LabwareFunctions } from '../' import * as Actions from '../actions' import { calibrationReducer } from '../reducer' @@ -19,7 +19,7 @@ describe('calibration reducer', () => { }) }) it('should handle a FETCH_LABWARE_CALIBRATION_SUCCESS', () => { - const action = LabwareActions.fetchLabwareCalibrationSuccess( + const action = LabwareFunctions.fetchLabwareCalibrationSuccess( 'robot-name', LabwareFixtures.mockAllLabwareCalibraton, {} diff --git a/app/src/calibration/index.js b/app/src/calibration/index.js index 3230debd047..b8c0518d865 100644 --- a/app/src/calibration/index.js +++ b/app/src/calibration/index.js @@ -1,5 +1,8 @@ // @flow // calibration data actions, selectors and constants +import * as labware from './labware' + export * from './actions' export * from './constants' export * from './selectors' +export { labware } diff --git a/app/src/calibration/labware/__fixtures__/labware-calibration.js b/app/src/calibration/labware/__fixtures__/labware-calibration.js index d3225d926fe..f6997a454b2 100644 --- a/app/src/calibration/labware/__fixtures__/labware-calibration.js +++ b/app/src/calibration/labware/__fixtures__/labware-calibration.js @@ -17,11 +17,11 @@ export const mockLabwareCalibration: LabwareCalibrationObjects = { calibrationData: { offset: { value: [0.0, 0.0, 0.0], - lastModified: '2020-03-27 19:43:24.642318', + lastModified: '2020-04-05T14:30', }, tipLength: { value: 30, - lastModified: '2020-03-27 19:43:24.642318', + lastModified: '2007-05-05T0:30', }, }, loadName: 'opentrons_96_tiprack_10ul', diff --git a/app/src/calibration/labware/selectors.js b/app/src/calibration/labware/selectors.js index d0c8898f60a..93ebcd9b7ca 100644 --- a/app/src/calibration/labware/selectors.js +++ b/app/src/calibration/labware/selectors.js @@ -6,6 +6,6 @@ import type { LabwareCalibrationObjects } from './../types' export const getListOfLabwareCalibrations = ( state: State, robotName: string -): Array | null => { +): Array | null => { return state.calibration[robotName]?.labwareCalibration?.data ?? null } diff --git a/app/src/calibration/reducer.js b/app/src/calibration/reducer.js index cdfc9975e9b..aeaf2dbc335 100644 --- a/app/src/calibration/reducer.js +++ b/app/src/calibration/reducer.js @@ -1,6 +1,6 @@ // @flow import * as Constants from './constants' -import { constants as LabwareConstants } from './labware' +import { labware } from '.' import type { Action } from '../types' import type { CalibrationState } from './types' @@ -22,7 +22,7 @@ export function calibrationReducer( }, } } - case LabwareConstants.FETCH_LABWARE_CALIBRATION_SUCCESS: { + case labware.FETCH_LABWARE_CALIBRATION_SUCCESS: { const { robotName, labwareCalibration } = action.payload return { ...state, diff --git a/app/src/components/FileInfo/ProtocolLabwareList.js b/app/src/components/FileInfo/ProtocolLabwareList.js index 0ba16ba16d3..33d58189dfe 100644 --- a/app/src/components/FileInfo/ProtocolLabwareList.js +++ b/app/src/components/FileInfo/ProtocolLabwareList.js @@ -1,23 +1,24 @@ // @flow import * as React from 'react' +import { css } from 'styled-components' import { - DIRECTION_COLUMN, + DIRECTION_ROW, Flex, Text, Icon, Tooltip, useHoverTooltip, - FLEX_AUTO, JUSTIFY_SPACE_BETWEEN, FONT_SIZE_BODY_1, FONT_WEIGHT_REGULAR, C_DARK_GRAY, TOOLTIP_AUTO, SPACING_1, + SIZE_1, + C_MED_GRAY, } from '@opentrons/components' import { SectionContentFlex } from '../layout' -import styles from './styles.css' export type ProtocolLabwareListProps = {| labware: Array, @@ -34,19 +35,19 @@ export function ProtocolLabwareList({ placement: TOOLTIP_AUTO, }) const iconComponent = ( - + ) + const TOOL_TIP_MESSAGE = 'calibrated offset from labware origin point' const toolTipComponent = ( - - calibrated offset from labware origin point - + {TOOL_TIP_MESSAGE} ) - const LABWARE_TYPE = "Type" - const LABWARE_QUANTITY = "Quantity" - const CALIBRATION_DATA = "Calibration Data" + const LABWARE_TYPE = 'Type' + const LABWARE_QUANTITY = 'Quantity' + const CALIBRATION_DATA = 'Calibration Data' + return ( {quantity.map((item, index) => ( - {item} + {item} ))} - +
{calibration}
diff --git a/app/src/components/FileInfo/__tests__/continue.test.js b/app/src/components/FileInfo/__tests__/continue.test.js index 494863fa17e..62d5971b833 100644 --- a/app/src/components/FileInfo/__tests__/continue.test.js +++ b/app/src/components/FileInfo/__tests__/continue.test.js @@ -1,6 +1,6 @@ // @flow import * as React from 'react' -import { Provider } from 'react-redux' +import { Link } from 'react-router-dom' import { mount } from 'enzyme' import { getCalibrateLocation, getRunLocation } from '../../../nav' @@ -27,6 +27,8 @@ const mockCalPath = '/path/to/cal' describe('Continue to run or calibration button component', () => { let mockStore let render + let props + let optional_link const CALIBRATE_SELECTOR = { id: 'calibrate', @@ -47,17 +49,21 @@ describe('Continue to run or calibration button component', () => { beforeEach(() => { mockGetCalNavigation.mockReturnValue(CALIBRATE_SELECTOR) mockGetRunNavigation.mockReturnValue(RUN_SELECTOR) + props = { + labwareCalibrated: false, + } + optional_link = mockCalPath mockStore = { subscribe: () => {}, getState: () => ({ state: true }), dispatch: jest.fn(), } - render = (labwareCalibrated: boolean = false) => + render = props => mount( - - - + + + ) }) diff --git a/app/src/components/FileInfo/index.js b/app/src/components/FileInfo/index.js index dbc9a8346bc..be9f5c8e17d 100644 --- a/app/src/components/FileInfo/index.js +++ b/app/src/components/FileInfo/index.js @@ -17,10 +17,7 @@ import { UploadError } from '../UploadError' import styles from './styles.css' import { selectors as robotSelectors } from '../../robot' -import { - selectors as labwareCalibrationSelectors, - actions as labwareActions, -} from '../../calibration' +import { labware as labwareFunctions } from '../../calibration' import type { State, Dispatch } from '../../types' @@ -40,7 +37,7 @@ export function FileInfo(props: FileInfoProps): React.Node { const { robot, sessionLoaded, sessionHasSteps } = props const { name: robotName } = robot React.useEffect(() => { - dispatch(labwareActions.fetchAllLabwareCalibrations(robotName)) + dispatch(labwareFunctions.fetchAllLabwareCalibrations(robotName)) }, [dispatch, robotName]) let uploadError = props.uploadError @@ -48,7 +45,7 @@ export function FileInfo(props: FileInfoProps): React.Node { robotSelectors.getLabware(state) ) const labwareCalibrations = useSelector((state: State) => - labwareCalibrationSelectors.getListOfLabwareCalibrations(state, robotName) + labwareFunctions.getListOfLabwareCalibrations(state, robotName) ) if (sessionLoaded && !uploadError && !sessionHasSteps) { diff --git a/app/src/components/FileInfo/styles.css b/app/src/components/FileInfo/styles.css index 85ef0f0ad35..235a0c0e2bf 100644 --- a/app/src/components/FileInfo/styles.css +++ b/app/src/components/FileInfo/styles.css @@ -115,12 +115,6 @@ margin: 0.5rem 0.5rem 0.5rem 0; } -.calibration_data_info_icon { - height: 1rem; - width: 1rem; - fill: var(--c-med-gray); - } - .warning_info_wrapper { display: flex; align-items: center; From be5edf978ca0a404ce79849f557d3eff2c550bec Mon Sep 17 00:00:00 2001 From: laura_danielle Date: Tue, 14 Jul 2020 09:10:09 -0400 Subject: [PATCH 07/18] Fixup tests, add parents --- app/src/calibration/__tests__/reducer.test.js | 2 +- .../labware/__tests__/actions.test.js | 4 +- app/src/calibration/labware/actions.js | 11 +- .../fetchLabwareCalibrationsEpic.test.js | 43 ++++++- .../epic/fetchAllLabwareCalibrationEpic.js | 10 +- app/src/components/FileInfo/Continue.js | 61 +++++++--- .../FileInfo/ProtocolLabwareCard.js | 30 ++++- .../FileInfo/ProtocolLabwareList.js | 29 +++-- .../FileInfo/__tests__/continue.test.js | 110 ++++++++++++------ .../FileInfo/__tests__/labwarecard.test.js | 45 +++++++ .../FileInfo/__tests__/labwarelist.test.js | 40 +++++++ app/src/components/FileInfo/index.js | 1 + app/src/components/FileInfo/styles.css | 5 +- .../components/layout/SectionContentFlex.js | 3 +- app/src/robot-api/__tests__/http.test.js | 9 ++ app/src/robot-api/http.js | 8 +- .../robot_server/service/labware/router.py | 5 +- 17 files changed, 325 insertions(+), 91 deletions(-) create mode 100644 app/src/components/FileInfo/__tests__/labwarecard.test.js create mode 100644 app/src/components/FileInfo/__tests__/labwarelist.test.js diff --git a/app/src/calibration/__tests__/reducer.test.js b/app/src/calibration/__tests__/reducer.test.js index c4a0a2ff85a..56b52f418ea 100644 --- a/app/src/calibration/__tests__/reducer.test.js +++ b/app/src/calibration/__tests__/reducer.test.js @@ -19,7 +19,7 @@ describe('calibration reducer', () => { }) }) it('should handle a FETCH_LABWARE_CALIBRATION_SUCCESS', () => { - const action = LabwareFunctions.fetchLabwareCalibrationSuccess( + const action = LabwareFunctions.fetchLabwareCalibrationsSuccess( 'robot-name', LabwareFixtures.mockAllLabwareCalibraton, {} diff --git a/app/src/calibration/labware/__tests__/actions.test.js b/app/src/calibration/labware/__tests__/actions.test.js index bc8294181ab..5ab52747f23 100644 --- a/app/src/calibration/labware/__tests__/actions.test.js +++ b/app/src/calibration/labware/__tests__/actions.test.js @@ -24,7 +24,7 @@ const SPECS: Array = [ }, { should: 'create a fetchCalibrationStatusSuccess action', - creator: Actions.fetchLabwareCalibrationSuccess, + creator: Actions.fetchLabwareCalibrationsSuccess, args: [ 'robot-name', Fixtures.mockFetchLabwareCalibrationSuccess.body, @@ -41,7 +41,7 @@ const SPECS: Array = [ }, { should: 'create a fetchCalibrationStatusFailure action', - creator: Actions.fetchLabwareCalibrationFailure, + creator: Actions.fetchLabwareCalibrationsFailure, args: [ 'robot-name', Fixtures.mockFetchLabwareCalibrationFailure.body, diff --git a/app/src/calibration/labware/actions.js b/app/src/calibration/labware/actions.js index a87696253b5..4a8647f9e00 100644 --- a/app/src/calibration/labware/actions.js +++ b/app/src/calibration/labware/actions.js @@ -10,14 +10,17 @@ import type { } from '../../robot-api/types' export const fetchAllLabwareCalibrations = ( - robotName: string + robotName: string, + loadName?: string, + namespace?: string, + version?: number ): Types.FetchLabwareCalibrationAction => ({ type: Constants.FETCH_ALL_LABWARE_CALIBRATIONS, - payload: { robotName }, + payload: { robotName, loadName, namespace, version }, meta: {}, }) -export const fetchLabwareCalibrationSuccess = ( +export const fetchLabwareCalibrationsSuccess = ( robotName: string, labwareCalibration: APITypes.AllLabwareCalibrations, meta: RobotApiRequestMeta @@ -27,7 +30,7 @@ export const fetchLabwareCalibrationSuccess = ( meta, }) -export const fetchLabwareCalibrationFailure = ( +export const fetchLabwareCalibrationsFailure = ( robotName: string, error: RobotApiErrorResponse, meta: RobotApiRequestMeta diff --git a/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js b/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js index 25899f4c575..cf2c3ac93d6 100644 --- a/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js +++ b/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js @@ -10,6 +10,19 @@ import { labwareCalibrationEpic } from '..' const makeTriggerActionAllCalibrations = robotName => Actions.fetchAllLabwareCalibrations(robotName) +const makeTriggerActionFilterCalibrations = ( + robotName, + loadName, + namespace, + version +) => + Actions.fetchAllLabwareCalibrations( + robotName, + (loadName = 'my_cute_labware'), + (namespace = 'cutelabwares'), + (version = 2) + ) + describe('fetch labware calibration epics', () => { afterEach(() => { jest.resetAllMocks() @@ -37,6 +50,32 @@ describe('fetch labware calibration epics', () => { }) }) + it('calls GET /labware/calibrations with query string', () => { + const mocks = setupEpicTestMocks( + makeTriggerActionFilterCalibrations, + Fixtures.mockFetchLabwareCalibrationSuccess + ) + + runEpicTest(mocks, ({ hot, expectObservable, flush }) => { + const action$ = hot('--a', { a: mocks.action }) + const state$ = hot('s-s', { s: mocks.state }) + const output$ = labwareCalibrationEpic(action$, state$) + + expectObservable(output$) + flush() + + expect(mocks.fetchRobotApi).toHaveBeenCalledWith(mocks.robot, { + method: 'GET', + path: '/labware/calibrations', + query: { + loadName: 'my_cute_labware', + namespace: 'cutelabwares', + version: 2, + }, + }) + }) + }) + it('maps successful response to FETCH_LABWARE_CALAIBRATION_SUCCESS', () => { const mocks = setupEpicTestMocks( makeTriggerActionAllCalibrations, @@ -49,7 +88,7 @@ describe('fetch labware calibration epics', () => { const output$ = labwareCalibrationEpic(action$, state$) expectObservable(output$).toBe('--a', { - a: Actions.fetchLabwareCalibrationSuccess( + a: Actions.fetchLabwareCalibrationsSuccess( mocks.robot.name, Fixtures.mockFetchLabwareCalibrationSuccess.body, { @@ -73,7 +112,7 @@ describe('fetch labware calibration epics', () => { const output$ = labwareCalibrationEpic(action$, state$) expectObservable(output$).toBe('--a', { - a: Actions.fetchLabwareCalibrationFailure( + a: Actions.fetchLabwareCalibrationsFailure( mocks.robot.name, Fixtures.mockFetchLabwareCalibrationFailure.body, { diff --git a/app/src/calibration/labware/epic/fetchAllLabwareCalibrationEpic.js b/app/src/calibration/labware/epic/fetchAllLabwareCalibrationEpic.js index 0a97cf08d6b..7fcc8da1249 100644 --- a/app/src/calibration/labware/epic/fetchAllLabwareCalibrationEpic.js +++ b/app/src/calibration/labware/epic/fetchAllLabwareCalibrationEpic.js @@ -1,7 +1,5 @@ // @flow import { ofType } from 'redux-observable' -import omitBy from 'lodash/omitBy' -import isEmpty from 'lodash/isEmpty' import { GET } from '../../../robot-api/constants' import { mapToRobotApiRequest } from '../../../robot-api/operators' @@ -17,13 +15,11 @@ import type { FetchLabwareCalibrationAction } from '../types' const mapActionToRequest: ActionToRequestMapper = action => { const { robotName, ...payloadWithoutRobot } = action.payload - // $FlowFixMe(lc, 2020-07-08): lodash flow types do not support keys with values of different types - const queryDict = omitBy(payloadWithoutRobot, isEmpty) return { method: GET, path: Constants.LABWARE_CALIBRATION_PATH, - query: queryDict, + query: payloadWithoutRobot, } } @@ -34,8 +30,8 @@ const mapResponseToAction: ResponseToActionMapper const { host, body, ...responseMeta } = response const meta = { ...originalAction.meta, response: responseMeta } return response.ok - ? Actions.fetchLabwareCalibrationSuccess(host.name, body, meta) - : Actions.fetchLabwareCalibrationFailure(host.name, body, meta) + ? Actions.fetchLabwareCalibrationsSuccess(host.name, body, meta) + : Actions.fetchLabwareCalibrationsFailure(host.name, body, meta) } export const fetchAllLabwareCalibrationEpic: Epic = (action$, state$) => { diff --git a/app/src/components/FileInfo/Continue.js b/app/src/components/FileInfo/Continue.js index 8f29bb7bd60..827fac3ee13 100644 --- a/app/src/components/FileInfo/Continue.js +++ b/app/src/components/FileInfo/Continue.js @@ -4,7 +4,17 @@ import { useSelector } from 'react-redux' import { Link } from 'react-router-dom' import { getCalibrateLocation, getRunLocation } from '../../nav' -import { PrimaryButton, HoverTooltip } from '@opentrons/components' +import { + PrimaryButton, + OutlineButton, + Flex, + useHoverTooltip, + Tooltip, + TOOLTIP_AUTO, + FONT_SIZE_CAPTION, + JUSTIFY_SPACE_BETWEEN, + Text, +} from '@opentrons/components' import styles from './styles.css' type ContinueProps = {| @@ -15,27 +25,50 @@ export function Continue({ labwareCalibrated }: ContinueProps): React.Node { const buttonText = labwareCalibrated ? 'Proceed to Run' : 'Proceed to Calibrate' + const primarySublabelText = labwareCalibrated + ? 'Use existing labware and pipette calibrations' + : 'Calibrate labware and pipette prior to run' + const secondaryButtonText = 'Re-calibrate' + const secondarySublabelText = 'Re-calibrate labware and pipette prior to run' const selector = labwareCalibrated ? getRunLocation : getCalibrateLocation + const { + path: secondaryButtonPath, + disabledReason: secondaryDisabledReason, + } = useSelector(getCalibrateLocation) const { path, disabledReason } = useSelector(selector) + const [targetProps, tooltipProps] = useHoverTooltip({ + placement: TOOLTIP_AUTO, + }) - // TODO(mc, 2019-11-26): tooltip positioning is all messed up with this component return ( - {disabledReason} : null} - placement="right" - > - {hoverTooltipHandlers => ( -
- + {labwareCalibrated && ( +
+ - {buttonText} - + {secondaryButtonText} + + {secondarySublabelText}
)} - +
+ + {buttonText} + + {disabledReason && ( + {disabledReason} + )} + {primarySublabelText} +
+ ) } diff --git a/app/src/components/FileInfo/ProtocolLabwareCard.js b/app/src/components/FileInfo/ProtocolLabwareCard.js index ba4bdf26e91..e5f67c9792f 100644 --- a/app/src/components/FileInfo/ProtocolLabwareCard.js +++ b/app/src/components/FileInfo/ProtocolLabwareCard.js @@ -2,8 +2,14 @@ // setup labware component import * as React from 'react' import round from 'lodash/round' +import { css } from 'styled-components' +import map from 'lodash/map' -import { ALIGN_CENTER, FONT_WEIGHT_SEMIBOLD } from '@opentrons/components' +import { + ALIGN_CENTER, + FONT_WEIGHT_SEMIBOLD, + SPACING_1, +} from '@opentrons/components' import { InfoSection } from './InfoSection' import { ProtocolLabwareList } from './ProtocolLabwareList' @@ -47,14 +53,34 @@ export function ProtocolLabwareCard({ } }) + const labwareCalibrationTable = ( + + {labwareCalibration} +
+ ) const labwareQuantity = Object.keys(labware).map(type => `x${labware[type]}`) + const labwareToParentMap = {} + Object.keys(labware).forEach(type => { + const parent = labwareCalibrations[type]?.attributes.parent ?? '' + const spacedParent = parent + .split(/(?=[A-Z])/) + .map(s => s.toUpperCase()) + .join(' ') + return (labwareToParentMap[type] = spacedParent) + }) + return ( ) diff --git a/app/src/components/FileInfo/ProtocolLabwareList.js b/app/src/components/FileInfo/ProtocolLabwareList.js index 33d58189dfe..f9a71444005 100644 --- a/app/src/components/FileInfo/ProtocolLabwareList.js +++ b/app/src/components/FileInfo/ProtocolLabwareList.js @@ -1,6 +1,5 @@ // @flow import * as React from 'react' -import { css } from 'styled-components' import { DIRECTION_ROW, @@ -13,26 +12,28 @@ import { FONT_SIZE_BODY_1, FONT_WEIGHT_REGULAR, C_DARK_GRAY, - TOOLTIP_AUTO, - SPACING_1, + TOOLTIP_TOP, SIZE_1, C_MED_GRAY, + SPACING_1, } from '@opentrons/components' import { SectionContentFlex } from '../layout' export type ProtocolLabwareListProps = {| - labware: Array, - quantity: Array, - calibration: Array, + labware: Array, + quantity: Array, + calibration: React.Node, + labwareToParent: Object, |} export function ProtocolLabwareList({ labware, quantity, calibration, + labwareToParent, }: ProtocolLabwareListProps): React.Node { const [targetProps, tooltipProps] = useHoverTooltip({ - placement: TOOLTIP_AUTO, + placement: TOOLTIP_TOP, }) const iconComponent = ( @@ -52,10 +53,14 @@ export function ProtocolLabwareList({ fontSize={FONT_SIZE_BODY_1} fontWeight={FONT_WEIGHT_REGULAR} color={C_DARK_GRAY} + border={SPACING_1} > {labware.map(name => ( - {name} +
+ {labwareToParent[name]} + {name} +
))}
@@ -69,13 +74,7 @@ export function ProtocolLabwareList({ toolTipComponent={toolTipComponent} toolTipProps={targetProps} > - - {calibration} -
+ {calibration}
) diff --git a/app/src/components/FileInfo/__tests__/continue.test.js b/app/src/components/FileInfo/__tests__/continue.test.js index 62d5971b833..8108a47d742 100644 --- a/app/src/components/FileInfo/__tests__/continue.test.js +++ b/app/src/components/FileInfo/__tests__/continue.test.js @@ -1,35 +1,55 @@ // @flow import * as React from 'react' -import { Link } from 'react-router-dom' +import { Provider } from 'react-redux' import { mount } from 'enzyme' +import noop from 'lodash/noop' -import { getCalibrateLocation, getRunLocation } from '../../../nav' -import { PrimaryButton } from '@opentrons/components' +import * as navigation from '../../../nav' +import { PrimaryButton, OutlineButton, Tooltip } from '@opentrons/components' import { Continue } from '../Continue' import type { State } from '../../../types' +import { MemoryRouter } from 'react-router-dom' jest.mock('../../../nav') -const mockGetCalNavigation: JestMockFn< +const MOCK_STATE: State = ({ mockState: true }: any) +const MOCK_STORE = { + getState: () => MOCK_STATE, + dispatch: noop, + subscribe: noop, +} + +const getCalibrateLocation: JestMockFn< [State], - $Call -> = getCalibrateLocation + $Call +> = navigation.getCalibrateLocation -const mockGetRunNavigation: JestMockFn< +const getRunLocation: JestMockFn< [State], - $Call -> = getRunLocation + $Call +> = navigation.getRunLocation + +function stubSelector(mock: JestMockFn<[State], R>, rVal: R) { + mock.mockImplementation(state => { + expect(state).toBe(MOCK_STATE) + return rVal + }) +} const mockRunPath = '/path/to/run' const mockCalPath = '/path/to/cal' describe('Continue to run or calibration button component', () => { - let mockStore - let render - let props - let optional_link - + const render = (labwareCalibrated: boolean = false) => { + return mount( + + + + + + ) + } const CALIBRATE_SELECTOR = { id: 'calibrate', path: mockCalPath, @@ -46,38 +66,54 @@ describe('Continue to run or calibration button component', () => { disabledReason: null, } + const CALIBRATE_SELECTOR_DISABLED = { + id: 'run', + path: mockRunPath, + title: 'RUN', + iconName: 'ot-run', + disabledReason: 'check your toolbox!', + } + beforeEach(() => { - mockGetCalNavigation.mockReturnValue(CALIBRATE_SELECTOR) - mockGetRunNavigation.mockReturnValue(RUN_SELECTOR) - props = { - labwareCalibrated: false, - } - optional_link = mockCalPath - - mockStore = { - subscribe: () => {}, - getState: () => ({ state: true }), - dispatch: jest.fn(), - } - render = props => - mount( - - - - ) + stubSelector(getCalibrateLocation, CALIBRATE_SELECTOR) + stubSelector(getRunLocation, RUN_SELECTOR) + }) + + afterEach(() => { + jest.resetAllMocks() }) it('Default button renders to continue to labware when not all labware is calibrated', () => { const wrapper = render() const button = wrapper.find(PrimaryButton) - expect(wrapper.children).toEqual('Proceed to Calibrate') - expect(button.props.path).toEqual(mockCalPath) + const secondarybutton = wrapper.find(OutlineButton) + const tooltip = wrapper.find(Tooltip) + + expect(tooltip.exists()).toEqual(false) + expect(button.children().text()).toEqual('Proceed to Calibrate') + expect(secondarybutton.exists()).toEqual(false) + expect(button.props().to).toEqual(mockCalPath) }) - it('renders nothing when calibration is OK', () => { + it('Run button renders when all labware is calibrated as well as secondary button', () => { const wrapper = render(true) const button = wrapper.find(PrimaryButton) - expect(wrapper.children).toEqual('Proceed to Run') - expect(button.props.path).toEqual(mockRunPath) + const secondarybutton = wrapper.find(OutlineButton) + const tooltip = wrapper.find(Tooltip) + + expect(tooltip.exists()).toEqual(false) + expect(button.children().text()).toEqual('Proceed to Run') + expect(secondarybutton.exists()).toEqual(true) + expect(button.props().to).toEqual(mockRunPath) + }) + + it('Test tool tip when disabled reason given', () => { + stubSelector(getCalibrateLocation, CALIBRATE_SELECTOR_DISABLED) + const wrapper = render() + const tooltip = wrapper.find(Tooltip) + expect(tooltip.exists()).toEqual(true) + expect(tooltip.prop('children')).toBe( + CALIBRATE_SELECTOR_DISABLED.disabledReason + ) }) }) diff --git a/app/src/components/FileInfo/__tests__/labwarecard.test.js b/app/src/components/FileInfo/__tests__/labwarecard.test.js new file mode 100644 index 00000000000..b9ac791d43d --- /dev/null +++ b/app/src/components/FileInfo/__tests__/labwarecard.test.js @@ -0,0 +1,45 @@ +// @flow +import * as React from 'react' +import { mount } from 'enzyme' + +import { ProtocolLabwareCard } from '../ProtocolLabwareCard' +import { ProtocolLabwareList } from '../ProtocolLabwareList' + +describe('Protocol Labware Card Component', () => { + const render = (labware: Object, labwareCalibrations: Object) => { + return mount( + + ) + } + + it('renders nothing when no labware exists in the protocol', () => { + const wrapper = render({}, {}) + expect(wrapper).toEqual({}) + }) + + it('passes in corectly formatted quantity and calibration to labware list', () => { + const wrapper = render( + { opentrons_labware: 2 }, + { + opentrons_labware: { + attributes: { + loadName: 'opentrons_labware', + calibrationData: { offset: { value: [1, 1, 1] } }, + }, + }, + } + ) + const labwareList = wrapper.find(ProtocolLabwareList) + const props = labwareList.props() + + expect(labwareList.exists()).toEqual(true) + expect(props.labware).toEqual(['opentrons_labware']) + expect(props.quantity).toEqual(['x2']) + const tbody = labwareList.find('tbody') + expect(tbody).toHaveLength(1) + expect(tbody.find('tr').find('td').length).toEqual(6) + }) +}) diff --git a/app/src/components/FileInfo/__tests__/labwarelist.test.js b/app/src/components/FileInfo/__tests__/labwarelist.test.js new file mode 100644 index 00000000000..9d2b42491c9 --- /dev/null +++ b/app/src/components/FileInfo/__tests__/labwarelist.test.js @@ -0,0 +1,40 @@ +// @flow +import * as React from 'react' +import { mount } from 'enzyme' + +import { + ProtocolLabwareList, + type ProtocolLabwareListProps, +} from '../ProtocolLabwareList' +import { SectionContentFlex } from '../../layout' + +describe('Protocol Labware List Component', () => { + const render = (renderProps: ProtocolLabwareListProps) => { + return mount() + } + + it('All three sections render, with tool tip', () => { + const randomTable = ( + + + + + + +
Not yet calibrated
+ ) + const wrapper = render({ + labware: ['opentrons_labware'], + quantity: ['x2'], + calibration: randomTable, + labwareToParent: { opentrons_labware: '' }, + }) + const sections = wrapper.find(SectionContentFlex) + const titleList = ['Type', 'Quantity', 'Calibration Data'] + + expect(sections.length).toEqual(3) + sections.forEach(section => + expect(titleList).toContain(section.props().title) + ) + }) +}) diff --git a/app/src/components/FileInfo/index.js b/app/src/components/FileInfo/index.js index be9f5c8e17d..549bf5cdc02 100644 --- a/app/src/components/FileInfo/index.js +++ b/app/src/components/FileInfo/index.js @@ -44,6 +44,7 @@ export function FileInfo(props: FileInfoProps): React.Node { const labware = useSelector((state: State) => robotSelectors.getLabware(state) ) + const labwareCalibrations = useSelector((state: State) => labwareFunctions.getListOfLabwareCalibrations(state, robotName) ) diff --git a/app/src/components/FileInfo/styles.css b/app/src/components/FileInfo/styles.css index 235a0c0e2bf..43dc6f95a3a 100644 --- a/app/src/components/FileInfo/styles.css +++ b/app/src/components/FileInfo/styles.css @@ -91,14 +91,11 @@ } } -.continue { - text-align: right; -} .continue_button { display: inline-block; width: 16.5rem; - margin-bottom: 1rem; + margin-bottom: 0.25rem; } .soft_warning { diff --git a/app/src/components/layout/SectionContentFlex.js b/app/src/components/layout/SectionContentFlex.js index 1774ba13976..73322f7120e 100644 --- a/app/src/components/layout/SectionContentFlex.js +++ b/app/src/components/layout/SectionContentFlex.js @@ -3,6 +3,7 @@ import * as React from 'react' import { SPACING_1, + SIZE_1, Flex, Box, ALIGN_CENTER, @@ -31,7 +32,7 @@ export function SectionContentFlex({ ...styleProps }: SectionContentFlexProps): React.Node { return ( - +

{title}

diff --git a/app/src/robot-api/__tests__/http.test.js b/app/src/robot-api/__tests__/http.test.js index 0b33cdcbe62..451cae96709 100644 --- a/app/src/robot-api/__tests__/http.test.js +++ b/app/src/robot-api/__tests__/http.test.js @@ -80,6 +80,15 @@ describe('robot-api http client', () => { ) }) + it('removes any empty query params', () => { + const url = robotApiUrl(robot, { + method: GET, + path: '/health', + query: { fakeParam: '' }, + }) + expect(url).toEqual(`http://127.0.0.1:${testPort}/health?`) + }) + it('can make a get request', () => { testApp.get('/health', (req, res) => { res.status(200).send('{ "hello": "world" }') diff --git a/app/src/robot-api/http.js b/app/src/robot-api/http.js index bab9f08144c..af055de13ed 100644 --- a/app/src/robot-api/http.js +++ b/app/src/robot-api/http.js @@ -4,6 +4,7 @@ import { of, from } from 'rxjs' import { map, switchMap, catchError } from 'rxjs/operators' import mapValues from 'lodash/mapValues' import toString from 'lodash/toString' +import omitBy from 'lodash/omitBy' import type { Observable } from 'rxjs' import type { @@ -12,6 +13,10 @@ import type { RobotApiResponse, } from './types' +function checkEmpty(value) { + return value == null || value === '' +} + export function robotApiUrl( host: RobotHost, request: RobotApiRequestOptions @@ -20,7 +25,8 @@ export function robotApiUrl( let url = `http://${host.ip}:${host.port}${path}` if (query && Object.keys(query).length > 0) { - const stringParamsMap = mapValues(query, toString) + const queryNoEmptyParams = omitBy(query, checkEmpty) + const stringParamsMap = mapValues(queryNoEmptyParams, toString) const queryParams = new URLSearchParams(stringParamsMap) url += `?${queryParams.toString()}` } diff --git a/robot-server/robot_server/service/labware/router.py b/robot-server/robot_server/service/labware/router.py index 0b9c1d5d387..d1de6cbdbdb 100644 --- a/robot-server/robot_server/service/labware/router.py +++ b/robot-server/robot_server/service/labware/router.py @@ -36,10 +36,13 @@ def _format_calibrations( tip_length = lw_models.TipData( value=tip_cal.value, lastModified=tip_cal.last_modified) + # TODO (lc, 07-13-2020) Once we categorize calibrations + # per slot, we should ensure that the parent displayed + # is the slot number. if calInfo.parent.module: parent_info = calInfo.parent.module else: - parent_info = calInfo.parent.slot + parent_info = '' cal_data = lw_models.CalibrationData( offset=offset, tipLength=tip_length) formatted_cal = lw_models.LabwareCalibration( From 87bab3a5e416498e5f37aadb0a9f8c4d4f1a30c8 Mon Sep 17 00:00:00 2001 From: laura_danielle Date: Tue, 14 Jul 2020 11:56:38 -0400 Subject: [PATCH 08/18] Fixup lint --- app/src/components/FileInfo/ProtocolLabwareCard.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/components/FileInfo/ProtocolLabwareCard.js b/app/src/components/FileInfo/ProtocolLabwareCard.js index e5f67c9792f..6d7e3921cad 100644 --- a/app/src/components/FileInfo/ProtocolLabwareCard.js +++ b/app/src/components/FileInfo/ProtocolLabwareCard.js @@ -3,7 +3,6 @@ import * as React from 'react' import round from 'lodash/round' import { css } from 'styled-components' -import map from 'lodash/map' import { ALIGN_CENTER, From f5a4043a2034ac8e2cdc8039578c97aff99959fe Mon Sep 17 00:00:00 2001 From: laura_danielle Date: Tue, 14 Jul 2020 12:48:36 -0400 Subject: [PATCH 09/18] Fixup failing test and css --- app/src/components/FileInfo/styles.css | 2 -- .../tests/service/labware/test_labware_calibration_access.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/components/FileInfo/styles.css b/app/src/components/FileInfo/styles.css index 43dc6f95a3a..4f26318e526 100644 --- a/app/src/components/FileInfo/styles.css +++ b/app/src/components/FileInfo/styles.css @@ -15,7 +15,6 @@ & > p { @apply --font-body-2-dark; } - } .title { @@ -91,7 +90,6 @@ } } - .continue_button { display: inline-block; width: 16.5rem; diff --git a/robot-server/tests/service/labware/test_labware_calibration_access.py b/robot-server/tests/service/labware/test_labware_calibration_access.py index b9140f57980..2cfd8853804 100644 --- a/robot-server/tests/service/labware/test_labware_calibration_access.py +++ b/robot-server/tests/service/labware/test_labware_calibration_access.py @@ -30,7 +30,7 @@ def test_access_individual_labware(api_client, grab_id): 'loadName': 'opentrons_96_tiprack_10ul', 'namespace': 'opentrons', 'version': 1, - 'parent': calibration_id} + 'parent': ''} resp = api_client.get(f'/labware/calibrations/{calibration_id}') assert resp.status_code == 200 From c6ef5b122bb6439569e7e6ef05bd4d135f3e86ce Mon Sep 17 00:00:00 2001 From: laura_danielle Date: Thu, 16 Jul 2020 10:50:26 -0400 Subject: [PATCH 10/18] More design changes --- app/src/components/FileInfo/ProtocolLabwareList.js | 5 ----- app/src/components/FileInfo/index.js | 3 ++- app/src/components/layout/SectionContentFlex.js | 7 +------ 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/app/src/components/FileInfo/ProtocolLabwareList.js b/app/src/components/FileInfo/ProtocolLabwareList.js index f9a71444005..612ac262a36 100644 --- a/app/src/components/FileInfo/ProtocolLabwareList.js +++ b/app/src/components/FileInfo/ProtocolLabwareList.js @@ -5,7 +5,6 @@ import { DIRECTION_ROW, Flex, Text, - Icon, Tooltip, useHoverTooltip, JUSTIFY_SPACE_BETWEEN, @@ -35,9 +34,6 @@ export function ProtocolLabwareList({ const [targetProps, tooltipProps] = useHoverTooltip({ placement: TOOLTIP_TOP, }) - const iconComponent = ( - - ) const TOOL_TIP_MESSAGE = 'calibrated offset from labware origin point' const toolTipComponent = ( {TOOL_TIP_MESSAGE} @@ -70,7 +66,6 @@ export function ProtocolLabwareList({ diff --git a/app/src/components/FileInfo/index.js b/app/src/components/FileInfo/index.js index 549bf5cdc02..3309157ce18 100644 --- a/app/src/components/FileInfo/index.js +++ b/app/src/components/FileInfo/index.js @@ -44,7 +44,8 @@ export function FileInfo(props: FileInfoProps): React.Node { const labware = useSelector((state: State) => robotSelectors.getLabware(state) ) - + console.log("RESULTS FROM LW SELECTOR") + console.log(labware) const labwareCalibrations = useSelector((state: State) => labwareFunctions.getListOfLabwareCalibrations(state, robotName) ) diff --git a/app/src/components/layout/SectionContentFlex.js b/app/src/components/layout/SectionContentFlex.js index 73322f7120e..bb50dec56aa 100644 --- a/app/src/components/layout/SectionContentFlex.js +++ b/app/src/components/layout/SectionContentFlex.js @@ -17,7 +17,6 @@ import type { export type SectionContentFlexProps = {| title: string, children: React.Node, - icon?: React.Node, toolTipComponent?: React.Node, toolTipProps?: UseHoverTooltipTargetProps, ...StyleProps, @@ -26,7 +25,6 @@ export type SectionContentFlexProps = {| export function SectionContentFlex({ title, children, - icon, toolTipComponent, toolTipProps, ...styleProps @@ -34,11 +32,8 @@ export function SectionContentFlex({ return ( - +

{title}

-
- - {icon} {toolTipComponent}
From af2e123bdc48b2cf9e351e575453d54e0e60dbb5 Mon Sep 17 00:00:00 2001 From: laura_danielle Date: Mon, 20 Jul 2020 09:55:42 -0400 Subject: [PATCH 11/18] More design changes --- app/src/calibration/index.js | 1 + app/src/components/FileInfo/Continue.js | 69 ++++---------- .../FileInfo/ProtocolLabwareCard.js | 88 +++++++++--------- .../FileInfo/ProtocolLabwareList.js | 89 +++++++++++-------- .../FileInfo/__tests__/continue.test.js | 37 ++------ .../FileInfo/__tests__/labwarecard.test.js | 19 ++-- .../FileInfo/__tests__/labwarelist.test.js | 39 ++++---- app/src/components/FileInfo/index.js | 42 ++++++--- app/src/components/FileInfo/styles.css | 4 + .../components/layout/SectionContentFlex.js | 43 --------- app/src/components/layout/index.js | 1 - 11 files changed, 180 insertions(+), 252 deletions(-) delete mode 100644 app/src/components/layout/SectionContentFlex.js diff --git a/app/src/calibration/index.js b/app/src/calibration/index.js index b8c0518d865..9a20f2b5d23 100644 --- a/app/src/calibration/index.js +++ b/app/src/calibration/index.js @@ -5,4 +5,5 @@ import * as labware from './labware' export * from './actions' export * from './constants' export * from './selectors' +export * from './types' export { labware } diff --git a/app/src/components/FileInfo/Continue.js b/app/src/components/FileInfo/Continue.js index 827fac3ee13..03b068abf16 100644 --- a/app/src/components/FileInfo/Continue.js +++ b/app/src/components/FileInfo/Continue.js @@ -3,72 +3,37 @@ import * as React from 'react' import { useSelector } from 'react-redux' import { Link } from 'react-router-dom' -import { getCalibrateLocation, getRunLocation } from '../../nav' +import { getCalibrateLocation } from '../../nav' import { PrimaryButton, - OutlineButton, - Flex, useHoverTooltip, Tooltip, TOOLTIP_AUTO, FONT_SIZE_CAPTION, - JUSTIFY_SPACE_BETWEEN, Text, } from '@opentrons/components' import styles from './styles.css' -type ContinueProps = {| - labwareCalibrated: boolean, -|} - -export function Continue({ labwareCalibrated }: ContinueProps): React.Node { - const buttonText = labwareCalibrated - ? 'Proceed to Run' - : 'Proceed to Calibrate' - const primarySublabelText = labwareCalibrated - ? 'Use existing labware and pipette calibrations' - : 'Calibrate labware and pipette prior to run' - const secondaryButtonText = 'Re-calibrate' - const secondarySublabelText = 'Re-calibrate labware and pipette prior to run' - const selector = labwareCalibrated ? getRunLocation : getCalibrateLocation - const { - path: secondaryButtonPath, - disabledReason: secondaryDisabledReason, - } = useSelector(getCalibrateLocation) - const { path, disabledReason } = useSelector(selector) +export function Continue(): React.Node { + const buttonText = 'Proceed to Calibrate' + const primarySublabelText = 'Verify pipette and labware calibrations' + const { path, disabledReason } = useSelector(getCalibrateLocation) const [targetProps, tooltipProps] = useHoverTooltip({ placement: TOOLTIP_AUTO, }) return ( - - {labwareCalibrated && ( -
- - {secondaryButtonText} - - {secondarySublabelText} -
- )} -
- - {buttonText} - - {disabledReason && ( - {disabledReason} - )} - {primarySublabelText} -
-
+
+ + {buttonText} + + {disabledReason && {disabledReason}} + {primarySublabelText} +
) } diff --git a/app/src/components/FileInfo/ProtocolLabwareCard.js b/app/src/components/FileInfo/ProtocolLabwareCard.js index 6d7e3921cad..cba381323bf 100644 --- a/app/src/components/FileInfo/ProtocolLabwareCard.js +++ b/app/src/components/FileInfo/ProtocolLabwareCard.js @@ -2,20 +2,24 @@ // setup labware component import * as React from 'react' import round from 'lodash/round' -import { css } from 'styled-components' import { ALIGN_CENTER, FONT_WEIGHT_SEMIBOLD, - SPACING_1, + Flex, + DIRECTION_ROW, + JUSTIFY_SPACE_BETWEEN, } from '@opentrons/components' import { InfoSection } from './InfoSection' import { ProtocolLabwareList } from './ProtocolLabwareList' +import type { LabwareCalibrationObjects } from '../../calibration' -type ProtocolLabwareProps = {| - labware: { string: number }, - labwareCalibrations: Object, // Note this should be { string: LabwareCalibrationObjects }, but flow was not passing +export type ProtocolLabwareProps = {| + labware: { + [key: string]: { count: number, display: string, parent: string }, + }, + labwareCalibrations: { [key: string]: LabwareCalibrationObjects }, |} const TITLE = 'Required Labware' @@ -26,61 +30,49 @@ export function ProtocolLabwareCard({ }: ProtocolLabwareProps): React.Node { if (Object.keys(labware).length === 0) return null - const labwareCalibration = Object.keys(labware).map(function(type, index) { + const labwareToParentMap = {} + Object.keys(labware).forEach((type, index) => { const currentLabware = labwareCalibrations[type]?.attributes + let calibrationData if (currentLabware) { const offset = currentLabware?.calibrationData?.offset.value - const X = round(offset[0], 2) - const Y = round(offset[1], 2) - const Z = round(offset[2], 2) - return ( - - X - {X} - Y - {Y} - Z - {Z} - + const X = parseFloat(round(offset[0], 1)).toFixed(1) + const Y = parseFloat(round(offset[1], 1)).toFixed(1) + const Z = parseFloat(round(offset[2], 1)).toFixed(1) + calibrationData = ( + +
X
+
{X}
+
Y
+
{Y}
+
Z
+
{Z}
+
) } else { - return ( - - Not yet calibrated - + calibrationData = ( + + Not yet calibrated + ) } - }) - const labwareCalibrationTable = ( - - {labwareCalibration} -
- ) - const labwareQuantity = Object.keys(labware).map(type => `x${labware[type]}`) - - const labwareToParentMap = {} - Object.keys(labware).forEach(type => { - const parent = labwareCalibrations[type]?.attributes.parent ?? '' - const spacedParent = parent - .split(/(?=[A-Z])/) - .map(s => s.toUpperCase()) - .join(' ') - return (labwareToParentMap[type] = spacedParent) + return (labwareToParentMap[type] = { + parent: labware[type].parent, + quantity: `x${labware[type].count}`, + display: labware[type].display, + calibration: calibrationData, + }) }) + console.log(labwareToParentMap) return ( - + ) } diff --git a/app/src/components/FileInfo/ProtocolLabwareList.js b/app/src/components/FileInfo/ProtocolLabwareList.js index 612ac262a36..12b662cb264 100644 --- a/app/src/components/FileInfo/ProtocolLabwareList.js +++ b/app/src/components/FileInfo/ProtocolLabwareList.js @@ -2,75 +2,86 @@ import * as React from 'react' import { - DIRECTION_ROW, Flex, Text, Tooltip, useHoverTooltip, - JUSTIFY_SPACE_BETWEEN, FONT_SIZE_BODY_1, FONT_WEIGHT_REGULAR, C_DARK_GRAY, TOOLTIP_TOP, - SIZE_1, - C_MED_GRAY, - SPACING_1, + SIZE_2, } from '@opentrons/components' -import { SectionContentFlex } from '../layout' +import { css } from 'styled-components' -export type ProtocolLabwareListProps = {| - labware: Array, - quantity: Array, +export type LoadNameMapProps = {| + parent: string, + quantity: string, + display: string, calibration: React.Node, - labwareToParent: Object, +|} + +export type ProtocolLabwareListProps = {| + loadNameMap: { [key: string]: LoadNameMapProps }, |} export function ProtocolLabwareList({ - labware, - quantity, - calibration, - labwareToParent, + loadNameMap, }: ProtocolLabwareListProps): React.Node { const [targetProps, tooltipProps] = useHoverTooltip({ placement: TOOLTIP_TOP, }) const TOOL_TIP_MESSAGE = 'calibrated offset from labware origin point' - const toolTipComponent = ( - {TOOL_TIP_MESSAGE} - ) const LABWARE_TYPE = 'Type' const LABWARE_QUANTITY = 'Quantity' const CALIBRATION_DATA = 'Calibration Data' return ( - - {labware.map(name => ( -
- {labwareToParent[name]} - {name} -
- ))} -
- - {quantity.map((item, index) => ( - {item} - ))} - - - {calibration} - + + + {LABWARE_TYPE} + {LABWARE_QUANTITY} + +
+ {CALIBRATION_DATA} + {TOOL_TIP_MESSAGE} +
+ + + {Object.keys(loadNameMap).map(type => { + const loadNameObject = loadNameMap[type] + return ( + + +
+ {loadNameObject.parent} + {loadNameObject.display} +
+ + {loadNameObject.quantity} + + {loadNameObject.calibration} + + + ) + })} + +
) } diff --git a/app/src/components/FileInfo/__tests__/continue.test.js b/app/src/components/FileInfo/__tests__/continue.test.js index 8108a47d742..7e0b3dda96c 100644 --- a/app/src/components/FileInfo/__tests__/continue.test.js +++ b/app/src/components/FileInfo/__tests__/continue.test.js @@ -25,11 +25,6 @@ const getCalibrateLocation: JestMockFn< $Call > = navigation.getCalibrateLocation -const getRunLocation: JestMockFn< - [State], - $Call -> = navigation.getRunLocation - function stubSelector(mock: JestMockFn<[State], R>, rVal: R) { mock.mockImplementation(state => { expect(state).toBe(MOCK_STATE) @@ -37,7 +32,6 @@ function stubSelector(mock: JestMockFn<[State], R>, rVal: R) { }) } -const mockRunPath = '/path/to/run' const mockCalPath = '/path/to/cal' describe('Continue to run or calibration button component', () => { @@ -45,7 +39,7 @@ describe('Continue to run or calibration button component', () => { return mount( - + ) @@ -58,25 +52,16 @@ describe('Continue to run or calibration button component', () => { disabledReason: null, } - const RUN_SELECTOR = { - id: 'run', - path: mockRunPath, - title: 'RUN', - iconName: 'ot-run', - disabledReason: null, - } - const CALIBRATE_SELECTOR_DISABLED = { - id: 'run', - path: mockRunPath, - title: 'RUN', - iconName: 'ot-run', + id: 'calibrate', + path: mockCalPath, + title: 'CALIBRATE', + iconName: 'ot-calibrate', disabledReason: 'check your toolbox!', } beforeEach(() => { stubSelector(getCalibrateLocation, CALIBRATE_SELECTOR) - stubSelector(getRunLocation, RUN_SELECTOR) }) afterEach(() => { @@ -95,18 +80,6 @@ describe('Continue to run or calibration button component', () => { expect(button.props().to).toEqual(mockCalPath) }) - it('Run button renders when all labware is calibrated as well as secondary button', () => { - const wrapper = render(true) - const button = wrapper.find(PrimaryButton) - const secondarybutton = wrapper.find(OutlineButton) - const tooltip = wrapper.find(Tooltip) - - expect(tooltip.exists()).toEqual(false) - expect(button.children().text()).toEqual('Proceed to Run') - expect(secondarybutton.exists()).toEqual(true) - expect(button.props().to).toEqual(mockRunPath) - }) - it('Test tool tip when disabled reason given', () => { stubSelector(getCalibrateLocation, CALIBRATE_SELECTOR_DISABLED) const wrapper = render() diff --git a/app/src/components/FileInfo/__tests__/labwarecard.test.js b/app/src/components/FileInfo/__tests__/labwarecard.test.js index b9ac791d43d..380c40d545a 100644 --- a/app/src/components/FileInfo/__tests__/labwarecard.test.js +++ b/app/src/components/FileInfo/__tests__/labwarecard.test.js @@ -2,11 +2,14 @@ import * as React from 'react' import { mount } from 'enzyme' -import { ProtocolLabwareCard } from '../ProtocolLabwareCard' +import { + ProtocolLabwareCard, + ProtocolLabwareProps, +} from '../ProtocolLabwareCard' import { ProtocolLabwareList } from '../ProtocolLabwareList' describe('Protocol Labware Card Component', () => { - const render = (labware: Object, labwareCalibrations: Object) => { + const render = ({ labware, labwareCalibrations }: ProtocolLabwareProps) => { return mount( { }) it('passes in corectly formatted quantity and calibration to labware list', () => { - const wrapper = render( - { opentrons_labware: 2 }, + const labwareParams = { + opentrons_labware: { + count: 2, + parent: '', + display: 'Opentrons Labware', + }, + } + const calibrationParams= { opentrons_labware: { attributes: { @@ -31,7 +40,7 @@ describe('Protocol Labware Card Component', () => { }, }, } - ) + const wrapper = render(labware={labwareParams} labwareCalibrations={calibrationParams}) const labwareList = wrapper.find(ProtocolLabwareList) const props = labwareList.props() diff --git a/app/src/components/FileInfo/__tests__/labwarelist.test.js b/app/src/components/FileInfo/__tests__/labwarelist.test.js index 9d2b42491c9..7cec5e89bfc 100644 --- a/app/src/components/FileInfo/__tests__/labwarelist.test.js +++ b/app/src/components/FileInfo/__tests__/labwarelist.test.js @@ -6,35 +6,30 @@ import { ProtocolLabwareList, type ProtocolLabwareListProps, } from '../ProtocolLabwareList' -import { SectionContentFlex } from '../../layout' describe('Protocol Labware List Component', () => { - const render = (renderProps: ProtocolLabwareListProps) => { - return mount() + const render = ({ loadNameMap }: ProtocolLabwareListProps) => { + return mount() } it('All three sections render, with tool tip', () => { - const randomTable = ( - - - - - - -
Not yet calibrated
- ) - const wrapper = render({ - labware: ['opentrons_labware'], - quantity: ['x2'], - calibration: randomTable, - labwareToParent: { opentrons_labware: '' }, - }) - const sections = wrapper.find(SectionContentFlex) + const randomDiv =
Not yet calibrated
+ const props = { + opentrons_labware: { + parent: '', + quantity: 'x2', + display: 'Opentrons Labware', + calibration: randomDiv, + }, + } + const wrapper = render(props) + const table = wrapper.find('tbody') + const headers = table.find('th') const titleList = ['Type', 'Quantity', 'Calibration Data'] - expect(sections.length).toEqual(3) - sections.forEach(section => - expect(titleList).toContain(section.props().title) + expect(table.length).toEqual(3) + headers.forEach(section => + expect(titleList).toContain(section.props().children) ) }) }) diff --git a/app/src/components/FileInfo/index.js b/app/src/components/FileInfo/index.js index 3309157ce18..a1d632f2c2b 100644 --- a/app/src/components/FileInfo/index.js +++ b/app/src/components/FileInfo/index.js @@ -1,11 +1,15 @@ // @flow import * as React from 'react' import { useSelector, useDispatch } from 'react-redux' +import { + getLabwareDisplayName, + getModuleDisplayName, +} from '@opentrons/shared-data' import filter from 'lodash/filter' -import every from 'lodash/every' import countBy from 'lodash/countBy' import keyBy from 'lodash/keyBy' +import forEach from 'lodash/forEach' // TODO(mc, 2018-09-13): these aren't cards; rename import { InformationCard } from './InformationCard' @@ -44,8 +48,10 @@ export function FileInfo(props: FileInfoProps): React.Node { const labware = useSelector((state: State) => robotSelectors.getLabware(state) ) - console.log("RESULTS FROM LW SELECTOR") - console.log(labware) + const modulesBySlot = useSelector((state: State) => + robotSelectors.getModulesBySlot(state) + ) + console.log(modulesBySlot) const labwareCalibrations = useSelector((state: State) => labwareFunctions.getListOfLabwareCalibrations(state, robotName) ) @@ -53,6 +59,7 @@ export function FileInfo(props: FileInfoProps): React.Node { if (sessionLoaded && !uploadError && !sessionHasSteps) { uploadError = { message: NO_STEPS_MESSAGE } } + const labwareCount = countBy(labware, 'type') const calibrations = filter(labwareCalibrations, function(l) { @@ -60,11 +67,28 @@ export function FileInfo(props: FileInfoProps): React.Node { }) const calibrationLoadNamesMap = keyBy(calibrations, function(labwareObject) { - return labwareObject?.attributes.loadName + const loadName = labwareObject?.attributes.loadName ?? '' + const parent = labwareObject?.attributes.parent ?? '' + return loadName + parent }) - const allLabwareCalibrated = every(Object.keys(labwareCount), function(a) { - return Object.keys(calibrationLoadNamesMap).includes(a) + const labwareDisplayNames = {} + forEach(labware, function(lw) { + const moduleName = modulesBySlot[lw.slot]?.model ?? '' + const keyValue = lw.type + moduleName + const existing = labwareDisplayNames[keyValue] + const parentName = (moduleName && getModuleDisplayName(moduleName)) || '' + if (!existing) { + const displayName = lw.definition && getLabwareDisplayName(lw.definition) + return (labwareDisplayNames[keyValue] = { + display: displayName, + count: 1, + parent: parentName, + }) + } else { + existing.count += 1 + return existing + } }) return ( @@ -73,13 +97,11 @@ export function FileInfo(props: FileInfoProps): React.Node { {uploadError && } - {sessionLoaded && !uploadError && ( - - )} + {sessionLoaded && !uploadError && }
) } diff --git a/app/src/components/FileInfo/styles.css b/app/src/components/FileInfo/styles.css index 4f26318e526..2a6a9d3c992 100644 --- a/app/src/components/FileInfo/styles.css +++ b/app/src/components/FileInfo/styles.css @@ -90,6 +90,10 @@ } } +.continue { + text-align: right; +} + .continue_button { display: inline-block; width: 16.5rem; diff --git a/app/src/components/layout/SectionContentFlex.js b/app/src/components/layout/SectionContentFlex.js deleted file mode 100644 index bb50dec56aa..00000000000 --- a/app/src/components/layout/SectionContentFlex.js +++ /dev/null @@ -1,43 +0,0 @@ -// @flow -import * as React from 'react' - -import { - SPACING_1, - SIZE_1, - Flex, - Box, - ALIGN_CENTER, - FLEX_AUTO, -} from '@opentrons/components' -import type { - UseHoverTooltipTargetProps, - StyleProps, -} from '@opentrons/components' - -export type SectionContentFlexProps = {| - title: string, - children: React.Node, - toolTipComponent?: React.Node, - toolTipProps?: UseHoverTooltipTargetProps, - ...StyleProps, -|} - -export function SectionContentFlex({ - title, - children, - toolTipComponent, - toolTipProps, - ...styleProps -}: SectionContentFlexProps): React.Node { - return ( - - - -

{title}

- {toolTipComponent} -
-
- {children} -
- ) -} diff --git a/app/src/components/layout/index.js b/app/src/components/layout/index.js index 4e66de47f38..16e6f8a0903 100644 --- a/app/src/components/layout/index.js +++ b/app/src/components/layout/index.js @@ -11,4 +11,3 @@ export * from './CardContentQuarter' export * from './CardContentFlex' export * from './SectionContentFull' export * from './SectionContentHalf' -export * from './SectionContentFlex' From 4b51f16ed28d8534a3bbf7a7bdbdba2d1ca40a72 Mon Sep 17 00:00:00 2001 From: laura_danielle Date: Mon, 20 Jul 2020 14:45:02 -0400 Subject: [PATCH 12/18] Fixup component structure changes --- .../FileInfo/ProtocolLabwareCard.js | 80 +++++++----------- .../FileInfo/ProtocolLabwareList.js | 47 ++++++++--- .../FileInfo/__tests__/index.test.js | 66 +++++++++++++++ .../FileInfo/__tests__/labwarecard.test.js | 83 ++++++++++++------- .../FileInfo/__tests__/labwarelist.test.js | 48 ++++++++--- app/src/components/FileInfo/index.js | 68 +-------------- app/src/components/FileInfo/styles.css | 2 +- app/src/protocol/selectors.js | 79 +++++++++++++++++- app/src/protocol/types.js | 9 ++ 9 files changed, 307 insertions(+), 175 deletions(-) create mode 100644 app/src/components/FileInfo/__tests__/index.test.js diff --git a/app/src/components/FileInfo/ProtocolLabwareCard.js b/app/src/components/FileInfo/ProtocolLabwareCard.js index cba381323bf..d08bb199600 100644 --- a/app/src/components/FileInfo/ProtocolLabwareCard.js +++ b/app/src/components/FileInfo/ProtocolLabwareCard.js @@ -1,74 +1,52 @@ // @flow // setup labware component import * as React from 'react' +import { useSelector, useDispatch } from 'react-redux' import round from 'lodash/round' -import { - ALIGN_CENTER, - FONT_WEIGHT_SEMIBOLD, - Flex, - DIRECTION_ROW, - JUSTIFY_SPACE_BETWEEN, -} from '@opentrons/components' - import { InfoSection } from './InfoSection' import { ProtocolLabwareList } from './ProtocolLabwareList' -import type { LabwareCalibrationObjects } from '../../calibration' +import * as labwareFunctions from '../../calibration' +import { associateLabwareWithCalibration } from '../../protocol' + +import type { State, Dispatch } from '../../types' export type ProtocolLabwareProps = {| - labware: { - [key: string]: { count: number, display: string, parent: string }, - }, - labwareCalibrations: { [key: string]: LabwareCalibrationObjects }, + robotName: string, |} const TITLE = 'Required Labware' export function ProtocolLabwareCard({ - labware, - labwareCalibrations, + robotName, }: ProtocolLabwareProps): React.Node { - if (Object.keys(labware).length === 0) return null - - const labwareToParentMap = {} - Object.keys(labware).forEach((type, index) => { - const currentLabware = labwareCalibrations[type]?.attributes - let calibrationData - if (currentLabware) { - const offset = currentLabware?.calibrationData?.offset.value - const X = parseFloat(round(offset[0], 1)).toFixed(1) - const Y = parseFloat(round(offset[1], 1)).toFixed(1) - const Z = parseFloat(round(offset[2], 1)).toFixed(1) - calibrationData = ( - -
X
-
{X}
-
Y
-
{Y}
-
Z
-
{Z}
-
- ) - } else { - calibrationData = ( - - Not yet calibrated - - ) + const dispatch = useDispatch() + React.useEffect(() => { + dispatch(labwareFunctions.fetchAllLabwareCalibrations(robotName)) + }, [dispatch, robotName]) + const labwareWithCalibration = useSelector((state: State) => + associateLabwareWithCalibration(state, robotName) + ) + if (labwareWithCalibration.length === 0) return null + + const labwareToParentMap = [] + labwareWithCalibration.map(labwareInfo => { + const offset = labwareInfo.calibration + let calibrationData = null + if (offset) { + const X = parseFloat(round(offset.x, 1)).toFixed(1) + const Y = parseFloat(round(offset.y, 1)).toFixed(1) + const Z = parseFloat(round(offset.z, 1)).toFixed(1) + calibrationData = { x: X, y: Y, z: Z } } - return (labwareToParentMap[type] = { - parent: labware[type].parent, - quantity: `x${labware[type].count}`, - display: labware[type].display, + labwareToParentMap.push({ + parent: labwareInfo.parent, + quantity: labwareInfo.quantity, + display: labwareInfo.display, calibration: calibrationData, }) }) - console.log(labwareToParentMap) return ( diff --git a/app/src/components/FileInfo/ProtocolLabwareList.js b/app/src/components/FileInfo/ProtocolLabwareList.js index 12b662cb264..9fe4d0f5e96 100644 --- a/app/src/components/FileInfo/ProtocolLabwareList.js +++ b/app/src/components/FileInfo/ProtocolLabwareList.js @@ -8,21 +8,25 @@ import { useHoverTooltip, FONT_SIZE_BODY_1, FONT_WEIGHT_REGULAR, + FONT_WEIGHT_SEMIBOLD, C_DARK_GRAY, TOOLTIP_TOP, - SIZE_2, + DIRECTION_ROW, + JUSTIFY_SPACE_BETWEEN, + SIZE_1, + ALIGN_CENTER, } from '@opentrons/components' import { css } from 'styled-components' export type LoadNameMapProps = {| parent: string, - quantity: string, + quantity: number, display: string, - calibration: React.Node, + calibration: {| x: string, y: string, z: string |} | null, |} export type ProtocolLabwareListProps = {| - loadNameMap: { [key: string]: LoadNameMapProps }, + loadNameMap: Array, |} export function ProtocolLabwareList({ @@ -35,6 +39,7 @@ export function ProtocolLabwareList({ const LABWARE_TYPE = 'Type' const LABWARE_QUANTITY = 'Quantity' const CALIBRATION_DATA = 'Calibration Data' + const NOT_CALIBRATED = 'Not yet calibrated' return ( @@ -59,23 +66,39 @@ export function ProtocolLabwareList({ - {Object.keys(loadNameMap).map(type => { - const loadNameObject = loadNameMap[type] + {loadNameMap.map((labwareObj, index) => { return ( - +
- {loadNameObject.parent} - {loadNameObject.display} + {labwareObj.parent} + {labwareObj.display}
- {loadNameObject.quantity} + {`x${labwareObj.quantity}`} - {loadNameObject.calibration} + {labwareObj.calibration ? ( + +
X
+
{labwareObj.calibration.x}
+
Y
+
{labwareObj.calibration.y}
+
Z
+
{labwareObj.calibration.z}
+
+ ) : ( + + {NOT_CALIBRATED} + + )} ) diff --git a/app/src/components/FileInfo/__tests__/index.test.js b/app/src/components/FileInfo/__tests__/index.test.js new file mode 100644 index 00000000000..0b850b39b70 --- /dev/null +++ b/app/src/components/FileInfo/__tests__/index.test.js @@ -0,0 +1,66 @@ +// @flow +import * as React from 'react' +import { Provider } from 'react-redux' +import { mount } from 'enzyme' +import noop from 'lodash/noop' + +import * as Fixtures from '../../../discovery/__fixtures__' +import { InformationCard } from '../InformationCard' +import { ProtocolPipettesCard } from '../ProtocolPipettesCard' +import { ProtocolModulesCard } from '../ProtocolModulesCard' +import { ProtocolLabwareCard } from '../ProtocolLabwareCard' +import { Continue } from '../Continue' +import { FileInfo } from '../' +import type { FileInfoProps } from '../' +import { UploadError } from '../../UploadError' + +import type { State } from '../../../types' + +const MOCK_STATE: State = ({ mockState: true }: any) +const MOCK_STORE = { + getState: () => MOCK_STATE, + dispatch: noop, + subscribe: noop, +} + +describe('File info Component', () => { + const render = (props: FileInfoProps) => { + return mount( + + + + ) + } + + it('renders all subcomponents when given correct parameters', () => { + const props = { + robot: Fixtures.mockConnectedRobot, + sessionLoaded: true, + sessionHasSteps: true, + uploadError: null, + } + const wrapper = render(props) + const labwareCard = wrapper.find(ProtocolLabwareCard) + expect(wrapper.find(InformationCard).exists()).toEqual(true) + expect(wrapper.find(ProtocolPipettesCard).exists()).toEqual(true) + expect(wrapper.find(ProtocolModulesCard).exists()).toEqual(true) + expect(wrapper.find(Continue).exists()).toEqual(true) + expect(labwareCard.props().robotName).toEqual(true) + }) + + it('An error renders when an upload error is given', () => { + const props = { + robot: Fixtures.mockConnectedRobot, + sessionLoaded: true, + sessionHasSteps: true, + uploadError: { message: 'Oh No!' }, + } + const wrapper = render(props) + const button = wrapper.find(Continue) + const uploadError = wrapper.find(UploadError) + // button should not render when upload error occurs + expect(button.exists()).toEqual(false) + expect(uploadError.exists()).toEqual(false) + expect(uploadError.props().uploadError.message).toEqual('Oh No!') + }) +}) diff --git a/app/src/components/FileInfo/__tests__/labwarecard.test.js b/app/src/components/FileInfo/__tests__/labwarecard.test.js index 380c40d545a..3380e1bdb74 100644 --- a/app/src/components/FileInfo/__tests__/labwarecard.test.js +++ b/app/src/components/FileInfo/__tests__/labwarecard.test.js @@ -2,51 +2,70 @@ import * as React from 'react' import { mount } from 'enzyme' -import { - ProtocolLabwareCard, - ProtocolLabwareProps, -} from '../ProtocolLabwareCard' +import type { State } from '../../../types' + +import { ProtocolLabwareCard } from '../ProtocolLabwareCard' import { ProtocolLabwareList } from '../ProtocolLabwareList' +import * as protocolSelect from '../../../protocol' + +jest.mock('../../../protocol') + +const MOCK_STATE: State = ({ mockState: true }: any) +const ROBOT_NAME = 'randomRobot' + +const associateLabwareWithCalibration: JestMockFn< + [State, string], + $Call +> = protocolSelect.associateLabwareWithCalibration + +function stubSelector(mock: JestMockFn<[State, string], R>, rVal: R) { + mock.mockImplementation(state => { + expect(state).toBe(MOCK_STATE) + return rVal + }) +} describe('Protocol Labware Card Component', () => { - const render = ({ labware, labwareCalibrations }: ProtocolLabwareProps) => { - return mount( - - ) + const render = (robotName = ROBOT_NAME) => { + return mount() } + const EMPTY_LABWARE = [] + const FULL_LABWARE = [ + { + quantity: 2, + calibration: null, + parent: '', + display: 'LABWARE DISPLAY NAME', + }, + ] + + beforeEach(() => { + stubSelector(associateLabwareWithCalibration, FULL_LABWARE) + }) + + afterEach(() => { + jest.resetAllMocks() + }) + it('renders nothing when no labware exists in the protocol', () => { - const wrapper = render({}, {}) + stubSelector(associateLabwareWithCalibration, EMPTY_LABWARE) + const wrapper = render() expect(wrapper).toEqual({}) }) it('passes in corectly formatted quantity and calibration to labware list', () => { - const labwareParams = { - opentrons_labware: { - count: 2, - parent: '', - display: 'Opentrons Labware', - }, - } - const calibrationParams= - { - opentrons_labware: { - attributes: { - loadName: 'opentrons_labware', - calibrationData: { offset: { value: [1, 1, 1] } }, - }, - }, - } - const wrapper = render(labware={labwareParams} labwareCalibrations={calibrationParams}) + const wrapper = render() const labwareList = wrapper.find(ProtocolLabwareList) const props = labwareList.props() - + const expected = { + quantity: 2, + calibration: 'Not yet calibrated', + parent: '', + display: 'LABWARE DIPSLAY NAME', + } expect(labwareList.exists()).toEqual(true) - expect(props.labware).toEqual(['opentrons_labware']) - expect(props.quantity).toEqual(['x2']) + expect(props.loadNameMap).toEqual(expected) const tbody = labwareList.find('tbody') expect(tbody).toHaveLength(1) expect(tbody.find('tr').find('td').length).toEqual(6) diff --git a/app/src/components/FileInfo/__tests__/labwarelist.test.js b/app/src/components/FileInfo/__tests__/labwarelist.test.js index 7cec5e89bfc..a1593eb97fe 100644 --- a/app/src/components/FileInfo/__tests__/labwarelist.test.js +++ b/app/src/components/FileInfo/__tests__/labwarelist.test.js @@ -2,34 +2,60 @@ import * as React from 'react' import { mount } from 'enzyme' -import { - ProtocolLabwareList, - type ProtocolLabwareListProps, -} from '../ProtocolLabwareList' +import { Tooltip } from '@opentrons/components' +import { ProtocolLabwareList } from '../ProtocolLabwareList' +import type { LoadNameMapProps } from '../ProtocolLabwareList' describe('Protocol Labware List Component', () => { - const render = ({ loadNameMap }: ProtocolLabwareListProps) => { + const render = loadNameMap => { return mount() } it('All three sections render, with tool tip', () => { - const randomDiv =
Not yet calibrated
- const props = { - opentrons_labware: { + const props: Array = [ + { parent: '', - quantity: 'x2', + quantity: 2, display: 'Opentrons Labware', - calibration: randomDiv, + calibration: null, }, - } + { + parent: 'MODULE GEN1', + quantity: 2, + display: 'Other Opentrons Labware', + calibration: { x: '1', y: '1', z: '1' }, + }, + ] const wrapper = render(props) const table = wrapper.find('tbody') const headers = table.find('th') + const rows = table.find('tr') + const calibrationTitle = headers.find('Calibration Data') + const tooltip = calibrationTitle.find(Tooltip) const titleList = ['Type', 'Quantity', 'Calibration Data'] + expect(tooltip.exists()).toEqual(true) expect(table.length).toEqual(3) headers.forEach(section => expect(titleList).toContain(section.props().children) ) + expect( + rows + .find('td') + .at(0) + .text() + ).toEqual() + expect( + rows + .find('td') + .at(1) + .text() + ).toEqual() + expect( + rows + .find('td') + .at(2) + .text() + ).toEqual() }) }) diff --git a/app/src/components/FileInfo/index.js b/app/src/components/FileInfo/index.js index a1d632f2c2b..354be77aa3e 100644 --- a/app/src/components/FileInfo/index.js +++ b/app/src/components/FileInfo/index.js @@ -1,15 +1,5 @@ // @flow import * as React from 'react' -import { useSelector, useDispatch } from 'react-redux' -import { - getLabwareDisplayName, - getModuleDisplayName, -} from '@opentrons/shared-data' - -import filter from 'lodash/filter' -import countBy from 'lodash/countBy' -import keyBy from 'lodash/keyBy' -import forEach from 'lodash/forEach' // TODO(mc, 2018-09-13): these aren't cards; rename import { InformationCard } from './InformationCard' @@ -20,11 +10,6 @@ import { Continue } from './Continue' import { UploadError } from '../UploadError' import styles from './styles.css' -import { selectors as robotSelectors } from '../../robot' -import { labware as labwareFunctions } from '../../calibration' - -import type { State, Dispatch } from '../../types' - import type { Robot } from '../../discovery/types' const NO_STEPS_MESSAGE = `This protocol has no steps in it - there's nothing for your robot to do! Your protocol needs at least one aspirate/dispense to import properly` @@ -37,69 +22,20 @@ export type FileInfoProps = {| |} export function FileInfo(props: FileInfoProps): React.Node { - const dispatch = useDispatch() const { robot, sessionLoaded, sessionHasSteps } = props - const { name: robotName } = robot - React.useEffect(() => { - dispatch(labwareFunctions.fetchAllLabwareCalibrations(robotName)) - }, [dispatch, robotName]) - let uploadError = props.uploadError - const labware = useSelector((state: State) => - robotSelectors.getLabware(state) - ) - const modulesBySlot = useSelector((state: State) => - robotSelectors.getModulesBySlot(state) - ) - console.log(modulesBySlot) - const labwareCalibrations = useSelector((state: State) => - labwareFunctions.getListOfLabwareCalibrations(state, robotName) - ) + let uploadError = props.uploadError if (sessionLoaded && !uploadError && !sessionHasSteps) { uploadError = { message: NO_STEPS_MESSAGE } } - const labwareCount = countBy(labware, 'type') - - const calibrations = filter(labwareCalibrations, function(l) { - return Object.keys(labwareCount).includes(l?.attributes.loadName) - }) - - const calibrationLoadNamesMap = keyBy(calibrations, function(labwareObject) { - const loadName = labwareObject?.attributes.loadName ?? '' - const parent = labwareObject?.attributes.parent ?? '' - return loadName + parent - }) - - const labwareDisplayNames = {} - forEach(labware, function(lw) { - const moduleName = modulesBySlot[lw.slot]?.model ?? '' - const keyValue = lw.type + moduleName - const existing = labwareDisplayNames[keyValue] - const parentName = (moduleName && getModuleDisplayName(moduleName)) || '' - if (!existing) { - const displayName = lw.definition && getLabwareDisplayName(lw.definition) - return (labwareDisplayNames[keyValue] = { - display: displayName, - count: 1, - parent: parentName, - }) - } else { - existing.count += 1 - return existing - } - }) - return (
- + {uploadError && } {sessionLoaded && !uploadError && }
diff --git a/app/src/components/FileInfo/styles.css b/app/src/components/FileInfo/styles.css index 2a6a9d3c992..38fb40a92ab 100644 --- a/app/src/components/FileInfo/styles.css +++ b/app/src/components/FileInfo/styles.css @@ -91,7 +91,7 @@ } .continue { - text-align: right; + text-align: right; } .continue_button { diff --git a/app/src/protocol/selectors.js b/app/src/protocol/selectors.js index 3a0906a1224..e7dde699a9d 100644 --- a/app/src/protocol/selectors.js +++ b/app/src/protocol/selectors.js @@ -3,14 +3,30 @@ import path from 'path' import startCase from 'lodash/startCase' import { createSelector } from 'reselect' import { getter } from '@thi.ng/paths' -import { getProtocolSchemaVersion } from '@opentrons/shared-data' +import { + getProtocolSchemaVersion, + getLabwareDisplayName, + getModuleDisplayName, +} from '@opentrons/shared-data' import { fileIsJson } from './protocol-data' import { createLogger } from '../logger' +import filter from 'lodash/filter' +import countBy from 'lodash/countBy' +import keyBy from 'lodash/keyBy' + import type { LabwareDefinition2 } from '@opentrons/shared-data' import type { ProtocolFile as SchemaV3ProtocolFile } from '@opentrons/shared-data/protocol/flowTypes/schemaV3' import type { State } from '../types' -import type { ProtocolData, ProtocolType, ProtocolFile } from './types' +import type { + ProtocolData, + ProtocolType, + ProtocolFile, + LabwareWithCalibration, +} from './types' + +import { getLabware, getModulesBySlot } from '../robot/selectors' +import { getListOfLabwareCalibrations } from '../calibration' type StringGetter = (?ProtocolData) => ?string type NumberGetter = (?ProtocolData) => ?number @@ -213,3 +229,62 @@ export const getProtocolMethod: StringSelector = createSelector( return `${METHOD_OT_API}${apiVersion !== null ? ` v${apiVersion}` : ''}` } ) + +export const associateLabwareWithCalibration: ( + state: State, + robotName: string +) => Array = createSelector< + State, + string, + Array, + _, + _, + _ +>( + (state: State) => getLabware(state), + getModulesBySlot, + getListOfLabwareCalibrations, + (labware, modulesBySlot, labwareCalibrations) => { + const updatedLabwareType = [] + labware.map(lw => { + const moduleName = modulesBySlot[lw.slot]?.model ?? '' + const newDataModel = { ...lw } + newDataModel.type = lw.type + moduleName + updatedLabwareType.push(newDataModel) + }) + + const labwareCount = countBy(updatedLabwareType, 'type') + const calibrations = filter(labwareCalibrations, function(l) { + return Object.keys(labwareCount).includes(l?.attributes.loadName) + }) + + const calibrationLoadNamesMap = keyBy(calibrations, function( + labwareObject + ) { + const loadName = labwareObject?.attributes.loadName ?? '' + const parent = labwareObject?.attributes.parent ?? '' + return loadName + parent + }) + + const labwareDisplayNames = [] + updatedLabwareType.map(lw => { + const moduleName = modulesBySlot[lw.slot]?.model ?? '' + const parentName = (moduleName && getModuleDisplayName(moduleName)) || '' + const data = + calibrationLoadNamesMap[lw.type]?.attributes.calibrationData.offset + .value + const calibrationData = data + ? { x: data[0], y: data[1], z: data[2] } + : null + const displayName = + (lw.definition && getLabwareDisplayName(lw.definition)) || '' + return labwareDisplayNames.push({ + display: displayName, + quantity: labwareCount[lw.type], + parent: parentName, + calibration: calibrationData, + }) + }) + return labwareDisplayNames + } +) diff --git a/app/src/protocol/types.js b/app/src/protocol/types.js index 0672803ccf6..b4dfe66a469 100644 --- a/app/src/protocol/types.js +++ b/app/src/protocol/types.js @@ -50,3 +50,12 @@ export type ProtocolState = $ReadOnly<{| contents: string | null, data: ProtocolData | null, |}> + +// selector types + +export type LabwareWithCalibration = {| + parent: string, + quantity: number, + display: string, + calibration: {| x: number, y: number, z: number |} | null, +|} From b9d5ea9d9be13238297900713fc6ddbd0a6ac969 Mon Sep 17 00:00:00 2001 From: laura_danielle Date: Thu, 23 Jul 2020 11:26:53 -0400 Subject: [PATCH 13/18] Fixup epics and actions --- app/src/calibration/__tests__/reducer.test.js | 2 +- app/src/calibration/api-types.js | 1 - .../fetchCalibrationStatusEpic.test.js | 2 +- app/src/calibration/epic/index.js | 4 +- app/src/calibration/index.js | 14 ++++- .../__fixtures__/labware-calibration.js | 1 - .../labware/__tests__/actions.test.js | 28 +++++++-- app/src/calibration/labware/actions.js | 6 +- .../fetchLabwareCalibrationsEpic.test.js | 57 ++++++++++++------- app/src/calibration/labware/types.js | 6 +- app/src/calibration/reducer.js | 2 +- app/src/epic.js | 6 +- app/src/robot-api/http.js | 4 +- app/src/robot-api/types.js | 2 +- 14 files changed, 89 insertions(+), 46 deletions(-) diff --git a/app/src/calibration/__tests__/reducer.test.js b/app/src/calibration/__tests__/reducer.test.js index 56b52f418ea..b87ab4ff0d2 100644 --- a/app/src/calibration/__tests__/reducer.test.js +++ b/app/src/calibration/__tests__/reducer.test.js @@ -2,7 +2,7 @@ import * as Fixtures from '../__fixtures__' import * as LabwareFixtures from '../labware/__fixtures__' -import { labware as LabwareFunctions } from '../' +import * as LabwareFunctions from '../labware' import * as Actions from '../actions' import { calibrationReducer } from '../reducer' diff --git a/app/src/calibration/api-types.js b/app/src/calibration/api-types.js index 7f7f50f25c5..62eeae65617 100644 --- a/app/src/calibration/api-types.js +++ b/app/src/calibration/api-types.js @@ -66,6 +66,5 @@ export type LabwareCalibrationObjects = {| export type AllLabwareCalibrations = {| data: Array, - type: string, meta: Object, |} diff --git a/app/src/calibration/epic/__tests__/fetchCalibrationStatusEpic.test.js b/app/src/calibration/epic/__tests__/fetchCalibrationStatusEpic.test.js index 50ccf2272e7..9393f8ec51b 100644 --- a/app/src/calibration/epic/__tests__/fetchCalibrationStatusEpic.test.js +++ b/app/src/calibration/epic/__tests__/fetchCalibrationStatusEpic.test.js @@ -2,7 +2,7 @@ import { setupEpicTestMocks, runEpicTest } from '../../../robot-api/__utils__' import * as Fixtures from '../../__fixtures__' import * as Actions from '../../actions' -import { calibrationEpic } from '..' +import { calibrationEpic } from '../../' const makeTriggerAction = robotName => Actions.fetchCalibrationStatus(robotName) diff --git a/app/src/calibration/epic/index.js b/app/src/calibration/epic/index.js index e0c5a4df1df..b7da8f093ef 100644 --- a/app/src/calibration/epic/index.js +++ b/app/src/calibration/epic/index.js @@ -5,4 +5,6 @@ import { fetchCalibrationStatusEpic } from './fetchCalibrationStatusEpic' import type { Epic } from '../../types' -export const calibrationEpic: Epic = combineEpics(fetchCalibrationStatusEpic) +export const calibrationStatusEpic: Epic = combineEpics( + fetchCalibrationStatusEpic +) diff --git a/app/src/calibration/index.js b/app/src/calibration/index.js index 9a20f2b5d23..466e8f1d1bc 100644 --- a/app/src/calibration/index.js +++ b/app/src/calibration/index.js @@ -1,9 +1,17 @@ // @flow // calibration data actions, selectors and constants -import * as labware from './labware' +import { combineEpics } from 'redux-observable' +import { calibrationStatusEpic } from './epic' +import { labwareCalibrationEpic } from './labware/epic' + +import type { Epic } from '../types' export * from './actions' export * from './constants' export * from './selectors' -export * from './types' -export { labware } +export * from './labware' + +export const calibrationEpic: Epic = combineEpics( + calibrationStatusEpic, + labwareCalibrationEpic +) diff --git a/app/src/calibration/labware/__fixtures__/labware-calibration.js b/app/src/calibration/labware/__fixtures__/labware-calibration.js index f6997a454b2..39de61c3296 100644 --- a/app/src/calibration/labware/__fixtures__/labware-calibration.js +++ b/app/src/calibration/labware/__fixtures__/labware-calibration.js @@ -36,7 +36,6 @@ export const mockLabwareCalibration: LabwareCalibrationObjects = { export const mockAllLabwareCalibraton: AllLabwareCalibrations = { data: [mockLabwareCalibration], meta: {}, - type: 'Labware Calibration', } export const { diff --git a/app/src/calibration/labware/__tests__/actions.test.js b/app/src/calibration/labware/__tests__/actions.test.js index 5ab52747f23..0c3f1577a93 100644 --- a/app/src/calibration/labware/__tests__/actions.test.js +++ b/app/src/calibration/labware/__tests__/actions.test.js @@ -13,17 +13,37 @@ type ActionSpec = {| const SPECS: Array = [ { - should: 'create a fetchCalibrationStatus action', + should: 'create a fetchLabwareCalibration action', creator: Actions.fetchAllLabwareCalibrations, args: ['robot-name'], expected: { type: 'calibration:FETCH_ALL_LABWARE_CALIBRATIONS', - payload: { robotName: 'robot-name' }, + payload: { + robotName: 'robot-name', + loadName: null, + namespace: null, + version: null, + }, + meta: {}, + }, + }, + { + should: 'create a fetchLabwareCalibration action with params', + creator: Actions.fetchAllLabwareCalibrations, + args: ['robot-name'], + expected: { + type: 'calibration:FETCH_ALL_LABWARE_CALIBRATIONS', + payload: { + robotName: 'robot-name', + loadName: 'fake_labware', + namespace: 'weird', + version: 1, + }, meta: {}, }, }, { - should: 'create a fetchCalibrationStatusSuccess action', + should: 'create a fetchLabwareCalibrationSuccess action', creator: Actions.fetchLabwareCalibrationsSuccess, args: [ 'robot-name', @@ -40,7 +60,7 @@ const SPECS: Array = [ }, }, { - should: 'create a fetchCalibrationStatusFailure action', + should: 'create a fetchLabwareCalibrationFailure action', creator: Actions.fetchLabwareCalibrationsFailure, args: [ 'robot-name', diff --git a/app/src/calibration/labware/actions.js b/app/src/calibration/labware/actions.js index 4a8647f9e00..56c43cbb5c2 100644 --- a/app/src/calibration/labware/actions.js +++ b/app/src/calibration/labware/actions.js @@ -11,9 +11,9 @@ import type { export const fetchAllLabwareCalibrations = ( robotName: string, - loadName?: string, - namespace?: string, - version?: number + loadName: string | null = null, + namespace: string | null = null, + version: number | null = null ): Types.FetchLabwareCalibrationAction => ({ type: Constants.FETCH_ALL_LABWARE_CALIBRATIONS, payload: { robotName, loadName, namespace, version }, diff --git a/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js b/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js index cf2c3ac93d6..eb7ef728017 100644 --- a/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js +++ b/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js @@ -10,19 +10,6 @@ import { labwareCalibrationEpic } from '..' const makeTriggerActionAllCalibrations = robotName => Actions.fetchAllLabwareCalibrations(robotName) -const makeTriggerActionFilterCalibrations = ( - robotName, - loadName, - namespace, - version -) => - Actions.fetchAllLabwareCalibrations( - robotName, - (loadName = 'my_cute_labware'), - (namespace = 'cutelabwares'), - (version = 2) - ) - describe('fetch labware calibration epics', () => { afterEach(() => { jest.resetAllMocks() @@ -45,16 +32,20 @@ describe('fetch labware calibration epics', () => { expect(mocks.fetchRobotApi).toHaveBeenCalledWith(mocks.robot, { method: 'GET', path: '/labware/calibrations', - query: {}, + query: { loadName: null, namespace: null, version: null }, }) }) }) - it('calls GET /labware/calibrations with query string', () => { - const mocks = setupEpicTestMocks( - makeTriggerActionFilterCalibrations, - Fixtures.mockFetchLabwareCalibrationSuccess - ) + it('calls GET /labware/calibrations with all possible queries', () => { + const mocks = setupEpicTestMocks(robotName => { + return Actions.fetchAllLabwareCalibrations( + robotName, + 'my_cute_labware', + 'cutelabwares', + 2 + ) + }, Fixtures.mockFetchLabwareCalibrationSuccess) runEpicTest(mocks, ({ hot, expectObservable, flush }) => { const action$ = hot('--a', { a: mocks.action }) @@ -75,6 +66,34 @@ describe('fetch labware calibration epics', () => { }) }) }) + it('calls GET /labware/calibrations with only some queries', () => { + const mocks = setupEpicTestMocks(robotName => { + return Actions.fetchAllLabwareCalibrations( + robotName, + null, + 'cutelabwares', + 2 + ) + }, Fixtures.mockFetchLabwareCalibrationSuccess) + + runEpicTest(mocks, ({ hot, expectObservable, flush }) => { + const action$ = hot('--a', { a: mocks.action }) + const state$ = hot('s-s', { s: mocks.state }) + const output$ = labwareCalibrationEpic(action$, state$) + + expectObservable(output$) + flush() + + expect(mocks.fetchRobotApi).toHaveBeenCalledWith(mocks.robot, { + method: 'GET', + path: '/labware/calibrations', + query: { + namespace: 'cutelabwares', + version: 2, + }, + }) + }) + }) it('maps successful response to FETCH_LABWARE_CALAIBRATION_SUCCESS', () => { const mocks = setupEpicTestMocks( diff --git a/app/src/calibration/labware/types.js b/app/src/calibration/labware/types.js index f98cfa338ac..f4be4f92a73 100644 --- a/app/src/calibration/labware/types.js +++ b/app/src/calibration/labware/types.js @@ -17,9 +17,9 @@ export type FetchLabwareCalibrationAction = {| type: FETCH_ALL_LABWARE_CALIBRATIONS, payload: {| robotName: string, - loadName?: string, - version?: number, - namespace?: string, + loadName: string | null, + version: number | null, + namespace: string | null, |}, meta: RobotApiRequestMeta, |} diff --git a/app/src/calibration/reducer.js b/app/src/calibration/reducer.js index aeaf2dbc335..c970eb48769 100644 --- a/app/src/calibration/reducer.js +++ b/app/src/calibration/reducer.js @@ -1,6 +1,6 @@ // @flow import * as Constants from './constants' -import { labware } from '.' +import * as labware from './labware' import type { Action } from '../types' import type { CalibrationState } from './types' diff --git a/app/src/epic.js b/app/src/epic.js index 0a42204e706..3ca15227daa 100644 --- a/app/src/epic.js +++ b/app/src/epic.js @@ -16,8 +16,7 @@ import { shellEpic } from './shell/epic' import { alertsEpic } from './alerts/epic' import { systemInfoEpic } from './system-info/epic' import { sessionsEpic } from './sessions/epic' -import { calibrationEpic } from './calibration/epic' -import { labwareCalibrationEpic } from './calibration/labware/epic' +import { calibrationEpic } from './calibration/' import type { Epic } from './types' @@ -36,6 +35,5 @@ export const rootEpic: Epic = combineEpics( alertsEpic, systemInfoEpic, sessionsEpic, - calibrationEpic, - labwareCalibrationEpic + calibrationEpic ) diff --git a/app/src/robot-api/http.js b/app/src/robot-api/http.js index af055de13ed..9f0c032646f 100644 --- a/app/src/robot-api/http.js +++ b/app/src/robot-api/http.js @@ -13,9 +13,7 @@ import type { RobotApiResponse, } from './types' -function checkEmpty(value) { - return value == null || value === '' -} +const checkEmpty = (val: mixed): boolean => val == null || val === '' export function robotApiUrl( host: RobotHost, diff --git a/app/src/robot-api/types.js b/app/src/robot-api/types.js index a901787290d..d9db363e333 100644 --- a/app/src/robot-api/types.js +++ b/app/src/robot-api/types.js @@ -14,7 +14,7 @@ export type RobotApiRequestOptions = {| method: Method, path: string, body?: { ... }, - query?: { [param: string]: string | boolean | number }, + query?: { [param: string]: string | boolean | number | null }, form?: FormData, |} From bc081d32f63f4d1b8eeb8fe34b78cb0f9aa12b82 Mon Sep 17 00:00:00 2001 From: laura_danielle Date: Thu, 23 Jul 2020 16:15:30 -0400 Subject: [PATCH 14/18] Fixup tests --- .../__fixtures__/associate-calibration.js | 17 ++++ .../calibration/labware/__fixtures__/index.js | 1 + .../__fixtures__/labware-calibration.js | 25 +++++- .../labware/__tests__/actions.test.js | 2 +- .../labware/__tests__/selectors.test.js | 71 +++++++++++++++- .../fetchLabwareCalibrationsEpic.test.js | 1 + app/src/calibration/labware/selectors.js | 74 +++++++++++++++++ app/src/calibration/labware/types.js | 9 ++ .../FileInfo/ProtocolLabwareCard.js | 3 +- .../FileInfo/__tests__/index.test.js | 25 ++---- .../FileInfo/__tests__/labwarecard.test.js | 31 ++++--- .../FileInfo/__tests__/labwarelist.test.js | 35 +++++--- app/src/protocol/selectors.js | 82 +------------------ app/src/protocol/types.js | 9 -- 14 files changed, 252 insertions(+), 133 deletions(-) create mode 100644 app/src/calibration/labware/__fixtures__/associate-calibration.js diff --git a/app/src/calibration/labware/__fixtures__/associate-calibration.js b/app/src/calibration/labware/__fixtures__/associate-calibration.js new file mode 100644 index 00000000000..6998d3dc61b --- /dev/null +++ b/app/src/calibration/labware/__fixtures__/associate-calibration.js @@ -0,0 +1,17 @@ +// @flow +import type { LabwareWithCalibration } from '../types' + +export const mockLabwareWithCalibration: Array = [ + { + quantity: 1, + display: '', + parent: '', + calibration: { x: 1, y: 1, z: 1 }, + }, + { + quantity: 1, + display: '', + parent: 'Magnetic Module GEN1', + calibration: null, + }, +] diff --git a/app/src/calibration/labware/__fixtures__/index.js b/app/src/calibration/labware/__fixtures__/index.js index 60de771de32..344c4eba005 100644 --- a/app/src/calibration/labware/__fixtures__/index.js +++ b/app/src/calibration/labware/__fixtures__/index.js @@ -1,2 +1,3 @@ // @flow export * from './labware-calibration' +export * from './associate-calibration' diff --git a/app/src/calibration/labware/__fixtures__/labware-calibration.js b/app/src/calibration/labware/__fixtures__/labware-calibration.js index 39de61c3296..a3b25e6cfd9 100644 --- a/app/src/calibration/labware/__fixtures__/labware-calibration.js +++ b/app/src/calibration/labware/__fixtures__/labware-calibration.js @@ -12,7 +12,7 @@ import type { AllLabwareCalibrations, } from '../../api-types' -export const mockLabwareCalibration: LabwareCalibrationObjects = { +export const mockLabwareCalibration1: LabwareCalibrationObjects = { attributes: { calibrationData: { offset: { @@ -33,8 +33,29 @@ export const mockLabwareCalibration: LabwareCalibrationObjects = { type: 'Labware Calibration', } +export const mockLabwareCalibration2: LabwareCalibrationObjects = { + attributes: { + calibrationData: { + offset: { + value: [1.0, 1.0, 1.0], + lastModified: '2020-04-05T14:30', + }, + tipLength: { + value: 30, + lastModified: '2007-05-05T0:30', + }, + }, + loadName: 'opentrons_96_tiprack_1000ul', + namespace: 'opentrons', + version: 1, + parent: '', + }, + id: 'some id', + type: 'Labware Calibration', +} + export const mockAllLabwareCalibraton: AllLabwareCalibrations = { - data: [mockLabwareCalibration], + data: [mockLabwareCalibration1, mockLabwareCalibration2], meta: {}, } diff --git a/app/src/calibration/labware/__tests__/actions.test.js b/app/src/calibration/labware/__tests__/actions.test.js index 0c3f1577a93..aec150fd691 100644 --- a/app/src/calibration/labware/__tests__/actions.test.js +++ b/app/src/calibration/labware/__tests__/actions.test.js @@ -30,7 +30,7 @@ const SPECS: Array = [ { should: 'create a fetchLabwareCalibration action with params', creator: Actions.fetchAllLabwareCalibrations, - args: ['robot-name'], + args: ['robot-name', 'fake_labware', 'weird', 1], expected: { type: 'calibration:FETCH_ALL_LABWARE_CALIBRATIONS', payload: { diff --git a/app/src/calibration/labware/__tests__/selectors.test.js b/app/src/calibration/labware/__tests__/selectors.test.js index 1f875bfb8a0..0327effa9d6 100644 --- a/app/src/calibration/labware/__tests__/selectors.test.js +++ b/app/src/calibration/labware/__tests__/selectors.test.js @@ -1,10 +1,28 @@ // @flow - import * as Fixtures from '../__fixtures__' import * as StatusFixtures from '../../__fixtures__' import * as Selectors from '../selectors' import type { State } from '../../../types' +import { selectors as robotSelectors } from '../../../robot' + +jest.mock('../../../robot') + +const getLabware: JestMockFn< + [State], + $Call +> = robotSelectors.getLabware + +const getModulesBySlot: JestMockFn< + [State], + $Call +> = robotSelectors.getModulesBySlot + +function stubSelector(mock: JestMockFn<[State], R>, rVal: R) { + mock.mockImplementation(state => { + return rVal + }) +} describe('labware calibration selectors', () => { it('should return null if no robot in state', () => { @@ -27,4 +45,55 @@ describe('labware calibration selectors', () => { Fixtures.mockAllLabwareCalibraton.data ) }) + it('should return a filtered list of calibrations', () => { + stubSelector(getLabware, [ + { + calibration: 'unconfirmed', + calibratorMount: 'right', + confirmed: false, + definition: null, + isLegacy: false, + isMoving: false, + isTiprack: true, + name: 'opentrons_96_tiprack_1000ul', + position: null, + slot: '2', + type: 'opentrons_96_tiprack_1000ul', + _id: 123, + }, + { + calibration: 'unconfirmed', + calibratorMount: null, + confirmed: false, + definition: null, + isLegacy: false, + isMoving: false, + isTiprack: false, + name: 'nest_96_wellplate_100ul_pcr_full_skirt', + position: null, + slot: '3', + type: 'nest_96_wellplate_100ul_pcr_full_skirt', + _id: 1234, + }, + ]) + stubSelector(getModulesBySlot, { + '3': { + model: 'magneticModuleV1', + slot: '3', + _id: 1945365648, + }, + }) + const state: $Shape = { + calibration: { + robotName: { + calibrationStatus: StatusFixtures.mockCalibrationStatus, + labwareCalibration: Fixtures.mockAllLabwareCalibraton, + }, + }, + } + + expect( + Selectors.associateLabwareWithCalibration(state, 'robotName') + ).toEqual(Fixtures.mockLabwareWithCalibration) + }) }) diff --git a/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js b/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js index eb7ef728017..5cbfc7c4196 100644 --- a/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js +++ b/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js @@ -88,6 +88,7 @@ describe('fetch labware calibration epics', () => { method: 'GET', path: '/labware/calibrations', query: { + loadName: null, namespace: 'cutelabwares', version: 2, }, diff --git a/app/src/calibration/labware/selectors.js b/app/src/calibration/labware/selectors.js index 93ebcd9b7ca..f91d68e3d07 100644 --- a/app/src/calibration/labware/selectors.js +++ b/app/src/calibration/labware/selectors.js @@ -1,7 +1,19 @@ // @flow +import { createSelector } from 'reselect' import type { State } from '../../types' import type { LabwareCalibrationObjects } from './../types' +import type { LabwareWithCalibration } from './types' +import { + getLabwareDisplayName, + getModuleDisplayName, +} from '@opentrons/shared-data' + +import filter from 'lodash/filter' +import countBy from 'lodash/countBy' +import keyBy from 'lodash/keyBy' + +import { selectors as robotSelectors } from '../../robot' export const getListOfLabwareCalibrations = ( state: State, @@ -9,3 +21,65 @@ export const getListOfLabwareCalibrations = ( ): Array | null => { return state.calibration[robotName]?.labwareCalibration?.data ?? null } + +// Note: due to some circular dependencies, this selector needs +// to live in calibration/labware though it should actually be +// a part of the protocol/ selectors +export const associateLabwareWithCalibration: ( + state: State, + robotName: string +) => Array = createSelector< + State, + string, + Array, + _, + _, + _ +>( + (state: State) => robotSelectors.getLabware(state), + robotSelectors.getModulesBySlot, + getListOfLabwareCalibrations, + (labware, modulesBySlot, labwareCalibrations) => { + const updatedLabwareType = [] + labware.map(lw => { + const moduleName = modulesBySlot[lw.slot]?.model ?? '' + const newDataModel = { ...lw } + newDataModel.type = lw.type + moduleName + updatedLabwareType.push(newDataModel) + }) + + const labwareCount = countBy(updatedLabwareType, 'type') + const calibrations = filter(labwareCalibrations, function(l) { + return Object.keys(labwareCount).includes(l?.attributes.loadName) + }) + + const calibrationLoadNamesMap = keyBy(calibrations, function( + labwareObject + ) { + const loadName = labwareObject?.attributes.loadName ?? '' + const parent = labwareObject?.attributes.parent ?? '' + return loadName + parent + }) + + const labwareDisplayNames = [] + updatedLabwareType.map(lw => { + const moduleName = modulesBySlot[lw.slot]?.model ?? '' + const parentName = (moduleName && getModuleDisplayName(moduleName)) || '' + const data = + calibrationLoadNamesMap[lw.type]?.attributes.calibrationData.offset + .value + const calibrationData = data + ? { x: data[0], y: data[1], z: data[2] } + : null + const displayName = + (lw.definition && getLabwareDisplayName(lw.definition)) || '' + return labwareDisplayNames.push({ + display: displayName, + quantity: labwareCount[lw.type], + parent: parentName, + calibration: calibrationData, + }) + }) + return labwareDisplayNames + } +) diff --git a/app/src/calibration/labware/types.js b/app/src/calibration/labware/types.js index f4be4f92a73..5aa70b2ecbf 100644 --- a/app/src/calibration/labware/types.js +++ b/app/src/calibration/labware/types.js @@ -39,6 +39,15 @@ export type FetchLabwareCalibrationFailureAction = {| meta: RobotApiRequestMeta, |} +// selector types + +export type LabwareWithCalibration = {| + parent: string, + quantity: number, + display: string, + calibration: {| x: number, y: number, z: number |} | null, +|} + export type LawareCalibrationAction = | FetchLabwareCalibrationAction | FetchAllLabwareCalibrationSuccessAction diff --git a/app/src/components/FileInfo/ProtocolLabwareCard.js b/app/src/components/FileInfo/ProtocolLabwareCard.js index d08bb199600..51ee5c5ea02 100644 --- a/app/src/components/FileInfo/ProtocolLabwareCard.js +++ b/app/src/components/FileInfo/ProtocolLabwareCard.js @@ -7,7 +7,6 @@ import round from 'lodash/round' import { InfoSection } from './InfoSection' import { ProtocolLabwareList } from './ProtocolLabwareList' import * as labwareFunctions from '../../calibration' -import { associateLabwareWithCalibration } from '../../protocol' import type { State, Dispatch } from '../../types' @@ -25,7 +24,7 @@ export function ProtocolLabwareCard({ dispatch(labwareFunctions.fetchAllLabwareCalibrations(robotName)) }, [dispatch, robotName]) const labwareWithCalibration = useSelector((state: State) => - associateLabwareWithCalibration(state, robotName) + labwareFunctions.associateLabwareWithCalibration(state, robotName) ) if (labwareWithCalibration.length === 0) return null diff --git a/app/src/components/FileInfo/__tests__/index.test.js b/app/src/components/FileInfo/__tests__/index.test.js index 0b850b39b70..15566312a62 100644 --- a/app/src/components/FileInfo/__tests__/index.test.js +++ b/app/src/components/FileInfo/__tests__/index.test.js @@ -1,8 +1,6 @@ // @flow import * as React from 'react' -import { Provider } from 'react-redux' -import { mount } from 'enzyme' -import noop from 'lodash/noop' +import { shallow } from 'enzyme' import * as Fixtures from '../../../discovery/__fixtures__' import { InformationCard } from '../InformationCard' @@ -14,22 +12,9 @@ import { FileInfo } from '../' import type { FileInfoProps } from '../' import { UploadError } from '../../UploadError' -import type { State } from '../../../types' - -const MOCK_STATE: State = ({ mockState: true }: any) -const MOCK_STORE = { - getState: () => MOCK_STATE, - dispatch: noop, - subscribe: noop, -} - describe('File info Component', () => { const render = (props: FileInfoProps) => { - return mount( - - - - ) + return shallow() } it('renders all subcomponents when given correct parameters', () => { @@ -41,11 +26,11 @@ describe('File info Component', () => { } const wrapper = render(props) const labwareCard = wrapper.find(ProtocolLabwareCard) - expect(wrapper.find(InformationCard).exists()).toEqual(true) + expect(wrapper.exists(InformationCard)).toBe(true) expect(wrapper.find(ProtocolPipettesCard).exists()).toEqual(true) expect(wrapper.find(ProtocolModulesCard).exists()).toEqual(true) expect(wrapper.find(Continue).exists()).toEqual(true) - expect(labwareCard.props().robotName).toEqual(true) + expect(labwareCard.props().robotName).toEqual('robot-name') }) it('An error renders when an upload error is given', () => { @@ -60,7 +45,7 @@ describe('File info Component', () => { const uploadError = wrapper.find(UploadError) // button should not render when upload error occurs expect(button.exists()).toEqual(false) - expect(uploadError.exists()).toEqual(false) + expect(wrapper.exists(UploadError)).toEqual(true) expect(uploadError.props().uploadError.message).toEqual('Oh No!') }) }) diff --git a/app/src/components/FileInfo/__tests__/labwarecard.test.js b/app/src/components/FileInfo/__tests__/labwarecard.test.js index 3380e1bdb74..a4a6ff8bf7d 100644 --- a/app/src/components/FileInfo/__tests__/labwarecard.test.js +++ b/app/src/components/FileInfo/__tests__/labwarecard.test.js @@ -1,22 +1,29 @@ // @flow import * as React from 'react' import { mount } from 'enzyme' +import { Provider } from 'react-redux' +import noop from 'lodash/noop' import type { State } from '../../../types' import { ProtocolLabwareCard } from '../ProtocolLabwareCard' import { ProtocolLabwareList } from '../ProtocolLabwareList' -import * as protocolSelect from '../../../protocol' +import * as labwareSelect from '../../../calibration' -jest.mock('../../../protocol') +jest.mock('../../../calibration') const MOCK_STATE: State = ({ mockState: true }: any) +const MOCK_STORE = { + getState: () => MOCK_STATE, + dispatch: noop, + subscribe: noop, +} const ROBOT_NAME = 'randomRobot' const associateLabwareWithCalibration: JestMockFn< [State, string], - $Call -> = protocolSelect.associateLabwareWithCalibration + $Call +> = labwareSelect.associateLabwareWithCalibration function stubSelector(mock: JestMockFn<[State, string], R>, rVal: R) { mock.mockImplementation(state => { @@ -27,7 +34,11 @@ function stubSelector(mock: JestMockFn<[State, string], R>, rVal: R) { describe('Protocol Labware Card Component', () => { const render = (robotName = ROBOT_NAME) => { - return mount() + return mount( + + + + ) } const EMPTY_LABWARE = [] @@ -59,15 +70,15 @@ describe('Protocol Labware Card Component', () => { const labwareList = wrapper.find(ProtocolLabwareList) const props = labwareList.props() const expected = { - quantity: 2, - calibration: 'Not yet calibrated', parent: '', - display: 'LABWARE DIPSLAY NAME', + quantity: 2, + display: 'LABWARE DISPLAY NAME', + calibration: null, } expect(labwareList.exists()).toEqual(true) - expect(props.loadNameMap).toEqual(expected) + expect(props.loadNameMap).toEqual([expected]) const tbody = labwareList.find('tbody') expect(tbody).toHaveLength(1) - expect(tbody.find('tr').find('td').length).toEqual(6) + expect(tbody.find('tr').find('td').length).toEqual(3) }) }) diff --git a/app/src/components/FileInfo/__tests__/labwarelist.test.js b/app/src/components/FileInfo/__tests__/labwarelist.test.js index a1593eb97fe..3522f70549b 100644 --- a/app/src/components/FileInfo/__tests__/labwarelist.test.js +++ b/app/src/components/FileInfo/__tests__/labwarelist.test.js @@ -21,7 +21,7 @@ describe('Protocol Labware List Component', () => { }, { parent: 'MODULE GEN1', - quantity: 2, + quantity: 1, display: 'Other Opentrons Labware', calibration: { x: '1', y: '1', z: '1' }, }, @@ -30,32 +30,47 @@ describe('Protocol Labware List Component', () => { const table = wrapper.find('tbody') const headers = table.find('th') const rows = table.find('tr') - const calibrationTitle = headers.find('Calibration Data') - const tooltip = calibrationTitle.find(Tooltip) + const tooltip = table.find(Tooltip) const titleList = ['Type', 'Quantity', 'Calibration Data'] expect(tooltip.exists()).toEqual(true) - expect(table.length).toEqual(3) - headers.forEach(section => - expect(titleList).toContain(section.props().children) - ) + expect(rows.length).toEqual(3) + headers.forEach(section => expect(titleList).toContain(section.text())) expect( rows .find('td') .at(0) .text() - ).toEqual() + ).toEqual('Opentrons Labware') expect( rows .find('td') .at(1) .text() - ).toEqual() + ).toEqual('x2') expect( rows .find('td') .at(2) .text() - ).toEqual() + ).toEqual('Not yet calibrated') + expect( + rows + .find('td') + .at(3) + .text() + ).toEqual('MODULE GEN1Other Opentrons Labware') + expect( + rows + .find('td') + .at(4) + .text() + ).toEqual('x1') + expect( + rows + .find('td') + .at(5) + .text() + ).toEqual('X1Y1Z1') }) }) diff --git a/app/src/protocol/selectors.js b/app/src/protocol/selectors.js index e7dde699a9d..61a58f8e0cb 100644 --- a/app/src/protocol/selectors.js +++ b/app/src/protocol/selectors.js @@ -1,32 +1,17 @@ // @flow +import { createSelector } from 'reselect' + import path from 'path' import startCase from 'lodash/startCase' -import { createSelector } from 'reselect' import { getter } from '@thi.ng/paths' -import { - getProtocolSchemaVersion, - getLabwareDisplayName, - getModuleDisplayName, -} from '@opentrons/shared-data' +import { getProtocolSchemaVersion } from '@opentrons/shared-data' import { fileIsJson } from './protocol-data' import { createLogger } from '../logger' -import filter from 'lodash/filter' -import countBy from 'lodash/countBy' -import keyBy from 'lodash/keyBy' - import type { LabwareDefinition2 } from '@opentrons/shared-data' import type { ProtocolFile as SchemaV3ProtocolFile } from '@opentrons/shared-data/protocol/flowTypes/schemaV3' import type { State } from '../types' -import type { - ProtocolData, - ProtocolType, - ProtocolFile, - LabwareWithCalibration, -} from './types' - -import { getLabware, getModulesBySlot } from '../robot/selectors' -import { getListOfLabwareCalibrations } from '../calibration' +import type { ProtocolData, ProtocolType, ProtocolFile } from './types' type StringGetter = (?ProtocolData) => ?string type NumberGetter = (?ProtocolData) => ?number @@ -229,62 +214,3 @@ export const getProtocolMethod: StringSelector = createSelector( return `${METHOD_OT_API}${apiVersion !== null ? ` v${apiVersion}` : ''}` } ) - -export const associateLabwareWithCalibration: ( - state: State, - robotName: string -) => Array = createSelector< - State, - string, - Array, - _, - _, - _ ->( - (state: State) => getLabware(state), - getModulesBySlot, - getListOfLabwareCalibrations, - (labware, modulesBySlot, labwareCalibrations) => { - const updatedLabwareType = [] - labware.map(lw => { - const moduleName = modulesBySlot[lw.slot]?.model ?? '' - const newDataModel = { ...lw } - newDataModel.type = lw.type + moduleName - updatedLabwareType.push(newDataModel) - }) - - const labwareCount = countBy(updatedLabwareType, 'type') - const calibrations = filter(labwareCalibrations, function(l) { - return Object.keys(labwareCount).includes(l?.attributes.loadName) - }) - - const calibrationLoadNamesMap = keyBy(calibrations, function( - labwareObject - ) { - const loadName = labwareObject?.attributes.loadName ?? '' - const parent = labwareObject?.attributes.parent ?? '' - return loadName + parent - }) - - const labwareDisplayNames = [] - updatedLabwareType.map(lw => { - const moduleName = modulesBySlot[lw.slot]?.model ?? '' - const parentName = (moduleName && getModuleDisplayName(moduleName)) || '' - const data = - calibrationLoadNamesMap[lw.type]?.attributes.calibrationData.offset - .value - const calibrationData = data - ? { x: data[0], y: data[1], z: data[2] } - : null - const displayName = - (lw.definition && getLabwareDisplayName(lw.definition)) || '' - return labwareDisplayNames.push({ - display: displayName, - quantity: labwareCount[lw.type], - parent: parentName, - calibration: calibrationData, - }) - }) - return labwareDisplayNames - } -) diff --git a/app/src/protocol/types.js b/app/src/protocol/types.js index b4dfe66a469..0672803ccf6 100644 --- a/app/src/protocol/types.js +++ b/app/src/protocol/types.js @@ -50,12 +50,3 @@ export type ProtocolState = $ReadOnly<{| contents: string | null, data: ProtocolData | null, |}> - -// selector types - -export type LabwareWithCalibration = {| - parent: string, - quantity: number, - display: string, - calibration: {| x: number, y: number, z: number |} | null, -|} From 8cfc073cf9d3a58fb369f5fc20f8943be2404612 Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Mon, 27 Jul 2020 16:25:56 -0400 Subject: [PATCH 15/18] fixup: revert changes superceded by #6211 --- robot-server/robot_server/service/labware/router.py | 5 +---- .../tests/service/labware/test_labware_calibration_access.py | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/robot-server/robot_server/service/labware/router.py b/robot-server/robot_server/service/labware/router.py index d1de6cbdbdb..0b9c1d5d387 100644 --- a/robot-server/robot_server/service/labware/router.py +++ b/robot-server/robot_server/service/labware/router.py @@ -36,13 +36,10 @@ def _format_calibrations( tip_length = lw_models.TipData( value=tip_cal.value, lastModified=tip_cal.last_modified) - # TODO (lc, 07-13-2020) Once we categorize calibrations - # per slot, we should ensure that the parent displayed - # is the slot number. if calInfo.parent.module: parent_info = calInfo.parent.module else: - parent_info = '' + parent_info = calInfo.parent.slot cal_data = lw_models.CalibrationData( offset=offset, tipLength=tip_length) formatted_cal = lw_models.LabwareCalibration( diff --git a/robot-server/tests/service/labware/test_labware_calibration_access.py b/robot-server/tests/service/labware/test_labware_calibration_access.py index 2cfd8853804..b9140f57980 100644 --- a/robot-server/tests/service/labware/test_labware_calibration_access.py +++ b/robot-server/tests/service/labware/test_labware_calibration_access.py @@ -30,7 +30,7 @@ def test_access_individual_labware(api_client, grab_id): 'loadName': 'opentrons_96_tiprack_10ul', 'namespace': 'opentrons', 'version': 1, - 'parent': ''} + 'parent': calibration_id} resp = api_client.get(f'/labware/calibrations/{calibration_id}') assert resp.status_code == 200 From 60b0bab241520612d1f21a3ccd7952f6c760b2e2 Mon Sep 17 00:00:00 2001 From: Mike Cousins Date: Tue, 28 Jul 2020 01:26:08 -0400 Subject: [PATCH 16/18] fixup: address review comments --- api/tests/opentrons/data/mad_mag_v2.py | 6 +- api/tests/opentrons/data/testosaur_v2.py | 1 + app/src/calibration/__tests__/reducer.test.js | 15 +- .../calibration/__tests__/selectors.test.js | 13 +- app/src/calibration/api-types.js | 10 +- .../fetchCalibrationStatusEpic.test.js | 2 +- app/src/calibration/epic/index.js | 6 +- app/src/calibration/index.js | 11 - .../__fixtures__/associate-calibration.js | 17 - .../calibration/labware/__fixtures__/index.js | 1 - .../__fixtures__/labware-calibration.js | 6 +- .../labware/__tests__/actions.test.js | 38 +-- .../labware/__tests__/selectors.test.js | 321 ++++++++++++++---- app/src/calibration/labware/actions.js | 25 +- app/src/calibration/labware/constants.js | 12 +- .../fetchLabwareCalibrationsEpic.test.js | 62 +--- ...pic.js => fetchLabwareCalibrationsEpic.js} | 21 +- app/src/calibration/labware/epic/index.js | 4 +- app/src/calibration/labware/selectors.js | 148 ++++---- app/src/calibration/labware/types.js | 40 +-- app/src/calibration/reducer.js | 32 +- app/src/calibration/selectors.js | 5 +- app/src/calibration/types.js | 7 +- app/src/components/FileInfo/Continue.js | 43 ++- .../FileInfo/ProtocolLabwareCard.js | 49 +-- .../FileInfo/ProtocolLabwareList.js | 180 +++++----- .../{continue.test.js => Continue.test.js} | 36 +- .../{index.test.js => FileInfo.test.js} | 4 +- .../__tests__/ProtocolLabwareCard.test.js | 82 +++++ .../__tests__/ProtocolLabwareList.test.js | 101 ++++++ .../FileInfo/__tests__/labwarecard.test.js | 84 ----- .../FileInfo/__tests__/labwarelist.test.js | 76 ----- app/src/components/FileInfo/index.js | 10 +- app/src/components/FileInfo/styles.css | 15 - app/src/epic.js | 2 +- app/src/protocol/selectors.js | 3 +- app/src/robot-api/__tests__/http.test.js | 9 +- app/src/robot-api/types.js | 2 +- components/src/primitives/Btn.js | 14 +- 39 files changed, 813 insertions(+), 700 deletions(-) delete mode 100644 app/src/calibration/labware/__fixtures__/associate-calibration.js rename app/src/calibration/labware/epic/{fetchAllLabwareCalibrationEpic.js => fetchLabwareCalibrationsEpic.js} (70%) rename app/src/components/FileInfo/__tests__/{continue.test.js => Continue.test.js} (68%) rename app/src/components/FileInfo/__tests__/{index.test.js => FileInfo.test.js} (92%) create mode 100644 app/src/components/FileInfo/__tests__/ProtocolLabwareCard.test.js create mode 100644 app/src/components/FileInfo/__tests__/ProtocolLabwareList.test.js delete mode 100644 app/src/components/FileInfo/__tests__/labwarecard.test.js delete mode 100644 app/src/components/FileInfo/__tests__/labwarelist.test.js diff --git a/api/tests/opentrons/data/mad_mag_v2.py b/api/tests/opentrons/data/mad_mag_v2.py index bdbb1e92605..1159855774e 100644 --- a/api/tests/opentrons/data/mad_mag_v2.py +++ b/api/tests/opentrons/data/mad_mag_v2.py @@ -13,8 +13,8 @@ def run(ctx): ctx.home() tr = ctx.load_labware('opentrons_96_tiprack_300ul', 1) mm = ctx.load_module('magnetic module', 4) - lw = mm.load_labware('nest_96_wellplate_100ul_pcr_fullskirt') - right = ctx.load_instrument('p300_single_gen2', types.Mount.RIGHT, [tr]) + lw = mm.load_labware('nest_96_wellplate_100ul_pcr_full_skirt') + right = ctx.load_instrument('p300_single', types.Mount.RIGHT, [tr]) mm.disengage() @@ -24,7 +24,7 @@ def run(ctx): mm.disengage() mm.engage(height=30) - mm.disenage() + mm.disengage() mm.engage(offset=-10) mm.disengage() diff --git a/api/tests/opentrons/data/testosaur_v2.py b/api/tests/opentrons/data/testosaur_v2.py index dbe06d79b45..d50e7a5d255 100644 --- a/api/tests/opentrons/data/testosaur_v2.py +++ b/api/tests/opentrons/data/testosaur_v2.py @@ -14,6 +14,7 @@ def run(ctx): tr = ctx.load_labware('opentrons_96_tiprack_300ul', 1) right = ctx.load_instrument('p300_single', types.Mount.RIGHT, [tr]) lw = ctx.load_labware('corning_96_wellplate_360ul_flat', 2) + ctx.pause() right.pick_up_tip() right.aspirate(10, lw.wells()[0].bottom()) right.dispense(10, lw.wells()[1].bottom()) diff --git a/app/src/calibration/__tests__/reducer.test.js b/app/src/calibration/__tests__/reducer.test.js index b87ab4ff0d2..aa50794dc5a 100644 --- a/app/src/calibration/__tests__/reducer.test.js +++ b/app/src/calibration/__tests__/reducer.test.js @@ -2,7 +2,7 @@ import * as Fixtures from '../__fixtures__' import * as LabwareFixtures from '../labware/__fixtures__' -import * as LabwareFunctions from '../labware' +import * as Labware from '../labware' import * as Actions from '../actions' import { calibrationReducer } from '../reducer' @@ -15,11 +15,15 @@ describe('calibration reducer', () => { ) expect(calibrationReducer({}, action)).toEqual({ - 'robot-name': { calibrationStatus: Fixtures.mockCalibrationStatus }, + 'robot-name': { + calibrationStatus: Fixtures.mockCalibrationStatus, + labwareCalibrations: null, + }, }) }) - it('should handle a FETCH_LABWARE_CALIBRATION_SUCCESS', () => { - const action = LabwareFunctions.fetchLabwareCalibrationsSuccess( + + it('should handle a FETCH_LABWARE_CALIBRATIONS_SUCCESS', () => { + const action = Labware.fetchLabwareCalibrationsSuccess( 'robot-name', LabwareFixtures.mockAllLabwareCalibraton, {} @@ -27,7 +31,8 @@ describe('calibration reducer', () => { expect(calibrationReducer({}, action)).toEqual({ 'robot-name': { - labwareCalibration: LabwareFixtures.mockAllLabwareCalibraton, + calibrationStatus: null, + labwareCalibrations: LabwareFixtures.mockAllLabwareCalibraton, }, }) }) diff --git a/app/src/calibration/__tests__/selectors.test.js b/app/src/calibration/__tests__/selectors.test.js index 5a314717667..423680edd65 100644 --- a/app/src/calibration/__tests__/selectors.test.js +++ b/app/src/calibration/__tests__/selectors.test.js @@ -12,12 +12,21 @@ describe('calibration selectors', () => { expect(Selectors.getCalibrationStatus(state, 'robotName')).toBe(null) }) + it('should return null if robot in state but no status', () => { + const state: $Shape = { + calibration: { + robotName: { calibrationStatus: null, labwareCalibrations: null }, + }, + } + expect(Selectors.getCalibrationStatus(state, 'robotName')).toBe(null) + }) + it('should return status if in state', () => { const state: $Shape = { calibration: { robotName: { calibrationStatus: Fixtures.mockCalibrationStatus, - labwareCalibration: null, + labwareCalibrations: null, }, }, } @@ -38,7 +47,7 @@ describe('calibration selectors', () => { calibration: { robotName: { calibrationStatus: Fixtures.mockCalibrationStatus, - labwareCalibration: null, + labwareCalibrations: null, }, }, } diff --git a/app/src/calibration/api-types.js b/app/src/calibration/api-types.js index 62eeae65617..3109a0c9e2f 100644 --- a/app/src/calibration/api-types.js +++ b/app/src/calibration/api-types.js @@ -50,7 +50,7 @@ export type CalibrationData = {| tipLength: TipLengthData | null, |} -export type SingleLabwareCalibration = {| +export type LabwareCalibration = {| calibrationData: CalibrationData, loadName: string, namespace: string, @@ -58,13 +58,13 @@ export type SingleLabwareCalibration = {| parent: string, |} -export type LabwareCalibrationObjects = {| - attributes: SingleLabwareCalibration, +export type LabwareCalibrationModel = {| + attributes: LabwareCalibration, type: string, id: string, |} export type AllLabwareCalibrations = {| - data: Array, - meta: Object, + data: Array, + meta: { ... }, |} diff --git a/app/src/calibration/epic/__tests__/fetchCalibrationStatusEpic.test.js b/app/src/calibration/epic/__tests__/fetchCalibrationStatusEpic.test.js index 9393f8ec51b..418a3f2a36a 100644 --- a/app/src/calibration/epic/__tests__/fetchCalibrationStatusEpic.test.js +++ b/app/src/calibration/epic/__tests__/fetchCalibrationStatusEpic.test.js @@ -2,7 +2,7 @@ import { setupEpicTestMocks, runEpicTest } from '../../../robot-api/__utils__' import * as Fixtures from '../../__fixtures__' import * as Actions from '../../actions' -import { calibrationEpic } from '../../' +import { calibrationEpic } from '../../epic' const makeTriggerAction = robotName => Actions.fetchCalibrationStatus(robotName) diff --git a/app/src/calibration/epic/index.js b/app/src/calibration/epic/index.js index b7da8f093ef..7784fa44122 100644 --- a/app/src/calibration/epic/index.js +++ b/app/src/calibration/epic/index.js @@ -2,9 +2,11 @@ import { combineEpics } from 'redux-observable' import { fetchCalibrationStatusEpic } from './fetchCalibrationStatusEpic' +import { labwareCalibrationEpic } from '../labware/epic' import type { Epic } from '../../types' -export const calibrationStatusEpic: Epic = combineEpics( - fetchCalibrationStatusEpic +export const calibrationEpic: Epic = combineEpics( + fetchCalibrationStatusEpic, + labwareCalibrationEpic ) diff --git a/app/src/calibration/index.js b/app/src/calibration/index.js index 466e8f1d1bc..6953c831795 100644 --- a/app/src/calibration/index.js +++ b/app/src/calibration/index.js @@ -1,17 +1,6 @@ // @flow // calibration data actions, selectors and constants -import { combineEpics } from 'redux-observable' -import { calibrationStatusEpic } from './epic' -import { labwareCalibrationEpic } from './labware/epic' - -import type { Epic } from '../types' - export * from './actions' export * from './constants' export * from './selectors' export * from './labware' - -export const calibrationEpic: Epic = combineEpics( - calibrationStatusEpic, - labwareCalibrationEpic -) diff --git a/app/src/calibration/labware/__fixtures__/associate-calibration.js b/app/src/calibration/labware/__fixtures__/associate-calibration.js deleted file mode 100644 index 6998d3dc61b..00000000000 --- a/app/src/calibration/labware/__fixtures__/associate-calibration.js +++ /dev/null @@ -1,17 +0,0 @@ -// @flow -import type { LabwareWithCalibration } from '../types' - -export const mockLabwareWithCalibration: Array = [ - { - quantity: 1, - display: '', - parent: '', - calibration: { x: 1, y: 1, z: 1 }, - }, - { - quantity: 1, - display: '', - parent: 'Magnetic Module GEN1', - calibration: null, - }, -] diff --git a/app/src/calibration/labware/__fixtures__/index.js b/app/src/calibration/labware/__fixtures__/index.js index 344c4eba005..60de771de32 100644 --- a/app/src/calibration/labware/__fixtures__/index.js +++ b/app/src/calibration/labware/__fixtures__/index.js @@ -1,3 +1,2 @@ // @flow export * from './labware-calibration' -export * from './associate-calibration' diff --git a/app/src/calibration/labware/__fixtures__/labware-calibration.js b/app/src/calibration/labware/__fixtures__/labware-calibration.js index a3b25e6cfd9..635c23c1288 100644 --- a/app/src/calibration/labware/__fixtures__/labware-calibration.js +++ b/app/src/calibration/labware/__fixtures__/labware-calibration.js @@ -8,11 +8,11 @@ import { LABWARE_CALIBRATION_PATH } from '../constants' import type { ResponseFixtures } from '../../../robot-api/__fixtures__' import type { - LabwareCalibrationObjects, + LabwareCalibrationModel, AllLabwareCalibrations, } from '../../api-types' -export const mockLabwareCalibration1: LabwareCalibrationObjects = { +export const mockLabwareCalibration1: LabwareCalibrationModel = { attributes: { calibrationData: { offset: { @@ -33,7 +33,7 @@ export const mockLabwareCalibration1: LabwareCalibrationObjects = { type: 'Labware Calibration', } -export const mockLabwareCalibration2: LabwareCalibrationObjects = { +export const mockLabwareCalibration2: LabwareCalibrationModel = { attributes: { calibrationData: { offset: { diff --git a/app/src/calibration/labware/__tests__/actions.test.js b/app/src/calibration/labware/__tests__/actions.test.js index aec150fd691..c4ea327a733 100644 --- a/app/src/calibration/labware/__tests__/actions.test.js +++ b/app/src/calibration/labware/__tests__/actions.test.js @@ -13,37 +13,17 @@ type ActionSpec = {| const SPECS: Array = [ { - should: 'create a fetchLabwareCalibration action', - creator: Actions.fetchAllLabwareCalibrations, + should: 'create a fetchLabwareCalibrations action', + creator: Actions.fetchLabwareCalibrations, args: ['robot-name'], expected: { - type: 'calibration:FETCH_ALL_LABWARE_CALIBRATIONS', - payload: { - robotName: 'robot-name', - loadName: null, - namespace: null, - version: null, - }, - meta: {}, - }, - }, - { - should: 'create a fetchLabwareCalibration action with params', - creator: Actions.fetchAllLabwareCalibrations, - args: ['robot-name', 'fake_labware', 'weird', 1], - expected: { - type: 'calibration:FETCH_ALL_LABWARE_CALIBRATIONS', - payload: { - robotName: 'robot-name', - loadName: 'fake_labware', - namespace: 'weird', - version: 1, - }, + type: 'calibration:FETCH_LABWARE_CALIBRATIONS', + payload: { robotName: 'robot-name' }, meta: {}, }, }, { - should: 'create a fetchLabwareCalibrationSuccess action', + should: 'create a fetchLabwareCalibrationsSuccess action', creator: Actions.fetchLabwareCalibrationsSuccess, args: [ 'robot-name', @@ -51,16 +31,16 @@ const SPECS: Array = [ { requestId: '123' }, ], expected: { - type: 'calibration:FETCH_LABWARE_CALIBRATION_SUCCESS', + type: 'calibration:FETCH_LABWARE_CALIBRATIONS_SUCCESS', payload: { robotName: 'robot-name', - labwareCalibration: Fixtures.mockAllLabwareCalibraton, + labwareCalibrations: Fixtures.mockAllLabwareCalibraton, }, meta: { requestId: '123' }, }, }, { - should: 'create a fetchLabwareCalibrationFailure action', + should: 'create a fetchLabwareCalibrationsFailure action', creator: Actions.fetchLabwareCalibrationsFailure, args: [ 'robot-name', @@ -68,7 +48,7 @@ const SPECS: Array = [ { requestId: '123' }, ], expected: { - type: 'calibration:FETCH_LABWARE_CALIBRATION_FAILURE', + type: 'calibration:FETCH_LABWARE_CALIBRATIONS_FAILURE', payload: { robotName: 'robot-name', error: Fixtures.mockFetchLabwareCalibrationFailure.body, diff --git a/app/src/calibration/labware/__tests__/selectors.test.js b/app/src/calibration/labware/__tests__/selectors.test.js index 0327effa9d6..3f6e1f67263 100644 --- a/app/src/calibration/labware/__tests__/selectors.test.js +++ b/app/src/calibration/labware/__tests__/selectors.test.js @@ -2,12 +2,17 @@ import * as Fixtures from '../__fixtures__' import * as StatusFixtures from '../../__fixtures__' import * as Selectors from '../selectors' +import { selectors as robotSelectors } from '../../../robot' + +import wellPlate96Def from '@opentrons/shared-data/labware/fixtures/2/fixture_96_plate.json' import type { State } from '../../../types' -import { selectors as robotSelectors } from '../../../robot' +import type { Labware as ProtocolLabware } from '../../../robot/types' jest.mock('../../../robot') +const robotName = 'robotName' + const getLabware: JestMockFn< [State], $Call @@ -18,82 +23,256 @@ const getModulesBySlot: JestMockFn< $Call > = robotSelectors.getModulesBySlot -function stubSelector(mock: JestMockFn<[State], R>, rVal: R) { - mock.mockImplementation(state => { - return rVal - }) -} - describe('labware calibration selectors', () => { - it('should return null if no robot in state', () => { - const state: $Shape = { calibration: {} } - expect(Selectors.getListOfLabwareCalibrations(state, 'robotName')).toBe( - null - ) - }) + describe('getLabwareCalibrations', () => { + it('should return empty array if no robot in state', () => { + const state: $Shape = { calibration: {} } + expect(Selectors.getLabwareCalibrations(state, robotName)).toEqual([]) + }) + + it('should return empty array if robot in state but no calibrations yet', () => { + const state: $Shape = { + calibration: { + robotName: { calibrationStatus: null, labwareCalibrations: null }, + }, + } + expect(Selectors.getLabwareCalibrations(state, robotName)).toEqual([]) + }) - it('should return list of calibrations if in state', () => { - const state: $Shape = { - calibration: { - robotName: { - calibrationStatus: StatusFixtures.mockCalibrationStatus, - labwareCalibration: Fixtures.mockAllLabwareCalibraton, + it('should return list of calibrations if in state', () => { + const state: $Shape = { + calibration: { + robotName: { + calibrationStatus: StatusFixtures.mockCalibrationStatus, + labwareCalibrations: Fixtures.mockAllLabwareCalibraton, + }, }, - }, - } - expect(Selectors.getListOfLabwareCalibrations(state, 'robotName')).toEqual( - Fixtures.mockAllLabwareCalibraton.data - ) + } + expect(Selectors.getLabwareCalibrations(state, robotName)).toEqual( + Fixtures.mockAllLabwareCalibraton.data + ) + }) }) - it('should return a filtered list of calibrations', () => { - stubSelector(getLabware, [ - { - calibration: 'unconfirmed', - calibratorMount: 'right', - confirmed: false, - definition: null, - isLegacy: false, - isMoving: false, - isTiprack: true, - name: 'opentrons_96_tiprack_1000ul', - position: null, - slot: '2', - type: 'opentrons_96_tiprack_1000ul', - _id: 123, - }, - { - calibration: 'unconfirmed', - calibratorMount: null, - confirmed: false, - definition: null, - isLegacy: false, - isMoving: false, - isTiprack: false, - name: 'nest_96_wellplate_100ul_pcr_full_skirt', - position: null, - slot: '3', - type: 'nest_96_wellplate_100ul_pcr_full_skirt', - _id: 1234, - }, - ]) - stubSelector(getModulesBySlot, { - '3': { - model: 'magneticModuleV1', - slot: '3', - _id: 1945365648, - }, + + describe('getProtocolLabwareList', () => { + let state: $Shape + + beforeEach(() => { + state = { calibration: {} } + + getLabware.mockImplementation(calledState => { + expect(calledState).toBe(state) + + return [ + ({ + type: wellPlate96Def.parameters.loadName, + definition: wellPlate96Def, + slot: '3', + }: $Shape), + ({ + type: 'some_v1_labware', + definition: null, + slot: '1', + }: $Shape), + ] + }) + + getModulesBySlot.mockImplementation(calledState => { + expect(calledState).toBe(state) + + return { + '3': { + model: 'magneticModuleV1', + slot: '3', + _id: 1945365648, + }, + } + }) + }) + + it('returns empty array if no labware in protocol', () => { + getLabware.mockReturnValue([]) + expect(Selectors.getProtocolLabwareList(state, robotName)).toEqual([]) }) - const state: $Shape = { - calibration: { - robotName: { - calibrationStatus: StatusFixtures.mockCalibrationStatus, - labwareCalibration: Fixtures.mockAllLabwareCalibraton, + + it('maps RPC labware to loadName and displayName', () => { + getModulesBySlot.mockReturnValue({}) + + expect(Selectors.getProtocolLabwareList(state, robotName)).toEqual([ + { + displayName: wellPlate96Def.metadata.displayName, + parentDisplayName: null, + quantity: 1, + calibration: null, }, - }, - } + { + displayName: 'some_v1_labware', + parentDisplayName: null, + quantity: 1, + calibration: null, + }, + ]) + }) - expect( - Selectors.associateLabwareWithCalibration(state, 'robotName') - ).toEqual(Fixtures.mockLabwareWithCalibration) + it('sets parentDisplayName if labware has module parent', () => { + expect(Selectors.getProtocolLabwareList(state, robotName)).toEqual([ + { + displayName: wellPlate96Def.metadata.displayName, + parentDisplayName: 'Magnetic Module GEN1', + quantity: 1, + calibration: null, + }, + { + displayName: 'some_v1_labware', + parentDisplayName: null, + quantity: 1, + calibration: null, + }, + ]) + }) + + it('grabs calibration data for labware if present, rounding to 1 decimal', () => { + getModulesBySlot.mockReturnValue({}) + const lwCalibration = Fixtures.mockLabwareCalibration1 + const { attributes } = lwCalibration + const { calibrationData } = attributes + + state = ({ + calibration: { + robotName: { + calibrationStatus: null, + labwareCalibrations: { + meta: {}, + data: [ + { + ...lwCalibration, + attributes: { + ...attributes, + loadName: wellPlate96Def.parameters.loadName, + namespace: wellPlate96Def.namespace, + version: wellPlate96Def.version, + calibrationData: { + ...calibrationData, + offset: { + ...calibrationData.offset, + value: [1.23, 4.56, 7.89], + }, + }, + }, + }, + ], + }, + }, + }, + }: $Shape) + + expect(Selectors.getProtocolLabwareList(state, robotName)).toEqual([ + { + displayName: wellPlate96Def.metadata.displayName, + parentDisplayName: null, + quantity: 1, + calibration: { x: 1.2, y: 4.6, z: 7.9 }, + }, + { + displayName: 'some_v1_labware', + parentDisplayName: null, + quantity: 1, + calibration: null, + }, + ]) + }) + + it('grabs calibration data for labware on module if present', () => { + const lwCalibration = Fixtures.mockLabwareCalibration1 + const { attributes } = lwCalibration + const { calibrationData } = attributes + + const calNotOnModule = { + ...lwCalibration, + attributes: { + ...attributes, + loadName: wellPlate96Def.parameters.loadName, + namespace: wellPlate96Def.namespace, + version: wellPlate96Def.version, + calibrationData: { + ...calibrationData, + offset: { + ...calibrationData.offset, + value: [0, 0, 0], + }, + }, + }, + } + + const calOnModule = { + ...lwCalibration, + attributes: { + ...attributes, + parent: 'magneticModuleV1', + loadName: wellPlate96Def.parameters.loadName, + namespace: wellPlate96Def.namespace, + version: wellPlate96Def.version, + calibrationData: { + ...calibrationData, + offset: { + ...calibrationData.offset, + value: [1.23, 4.56, 7.89], + }, + }, + }, + } + + state = ({ + calibration: { + robotName: { + calibrationStatus: null, + labwareCalibrations: { + meta: {}, + data: [calNotOnModule, calOnModule], + }, + }, + }, + }: $Shape) + + expect(Selectors.getProtocolLabwareList(state, robotName)).toEqual([ + { + displayName: wellPlate96Def.metadata.displayName, + parentDisplayName: 'Magnetic Module GEN1', + quantity: 1, + calibration: { x: 1.2, y: 4.6, z: 7.9 }, + }, + { + displayName: 'some_v1_labware', + parentDisplayName: null, + quantity: 1, + calibration: null, + }, + ]) + }) + + it('flattens out duplicate labware using quantity', () => { + getLabware.mockReturnValue([ + ({ + type: wellPlate96Def.parameters.loadName, + definition: wellPlate96Def, + slot: '3', + }: $Shape), + ({ + type: wellPlate96Def.parameters.loadName, + definition: wellPlate96Def, + slot: '3', + }: $Shape), + ]) + getModulesBySlot.mockReturnValue({}) + + expect(Selectors.getProtocolLabwareList(state, robotName)).toEqual([ + { + displayName: wellPlate96Def.metadata.displayName, + parentDisplayName: null, + quantity: 2, + calibration: null, + }, + ]) + }) }) }) diff --git a/app/src/calibration/labware/actions.js b/app/src/calibration/labware/actions.js index 56c43cbb5c2..2bba4a6c4e2 100644 --- a/app/src/calibration/labware/actions.js +++ b/app/src/calibration/labware/actions.js @@ -9,24 +9,21 @@ import type { RobotApiErrorResponse, } from '../../robot-api/types' -export const fetchAllLabwareCalibrations = ( - robotName: string, - loadName: string | null = null, - namespace: string | null = null, - version: number | null = null -): Types.FetchLabwareCalibrationAction => ({ - type: Constants.FETCH_ALL_LABWARE_CALIBRATIONS, - payload: { robotName, loadName, namespace, version }, +export const fetchLabwareCalibrations = ( + robotName: string +): Types.FetchLabwareCalibrationsAction => ({ + type: Constants.FETCH_LABWARE_CALIBRATIONS, + payload: { robotName }, meta: {}, }) export const fetchLabwareCalibrationsSuccess = ( robotName: string, - labwareCalibration: APITypes.AllLabwareCalibrations, + labwareCalibrations: APITypes.AllLabwareCalibrations, meta: RobotApiRequestMeta -): Types.FetchAllLabwareCalibrationSuccessAction => ({ - type: Constants.FETCH_LABWARE_CALIBRATION_SUCCESS, - payload: { robotName, labwareCalibration }, +): Types.FetchLabwareCalibrationsSuccessAction => ({ + type: Constants.FETCH_LABWARE_CALIBRATIONS_SUCCESS, + payload: { robotName, labwareCalibrations }, meta, }) @@ -34,8 +31,8 @@ export const fetchLabwareCalibrationsFailure = ( robotName: string, error: RobotApiErrorResponse, meta: RobotApiRequestMeta -): Types.FetchLabwareCalibrationFailureAction => ({ - type: Constants.FETCH_LABWARE_CALIBRATION_FAILURE, +): Types.FetchLabwareCalibrationsFailureAction => ({ + type: Constants.FETCH_LABWARE_CALIBRATIONS_FAILURE, payload: { robotName, error }, meta, }) diff --git a/app/src/calibration/labware/constants.js b/app/src/calibration/labware/constants.js index 7034a15350d..e1d34c5b440 100644 --- a/app/src/calibration/labware/constants.js +++ b/app/src/calibration/labware/constants.js @@ -3,11 +3,11 @@ export const LABWARE_CALIBRATION_PATH: '/labware/calibrations' = '/labware/calibrations' -export const FETCH_ALL_LABWARE_CALIBRATIONS: 'calibration:FETCH_ALL_LABWARE_CALIBRATIONS' = - 'calibration:FETCH_ALL_LABWARE_CALIBRATIONS' +export const FETCH_LABWARE_CALIBRATIONS: 'calibration:FETCH_LABWARE_CALIBRATIONS' = + 'calibration:FETCH_LABWARE_CALIBRATIONS' -export const FETCH_LABWARE_CALIBRATION_SUCCESS: 'calibration:FETCH_LABWARE_CALIBRATION_SUCCESS' = - 'calibration:FETCH_LABWARE_CALIBRATION_SUCCESS' +export const FETCH_LABWARE_CALIBRATIONS_SUCCESS: 'calibration:FETCH_LABWARE_CALIBRATIONS_SUCCESS' = + 'calibration:FETCH_LABWARE_CALIBRATIONS_SUCCESS' -export const FETCH_LABWARE_CALIBRATION_FAILURE: 'calibration:FETCH_LABWARE_CALIBRATION_FAILURE' = - 'calibration:FETCH_LABWARE_CALIBRATION_FAILURE' +export const FETCH_LABWARE_CALIBRATIONS_FAILURE: 'calibration:FETCH_LABWARE_CALIBRATIONS_FAILURE' = + 'calibration:FETCH_LABWARE_CALIBRATIONS_FAILURE' diff --git a/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js b/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js index 5cbfc7c4196..5611f9877cd 100644 --- a/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js +++ b/app/src/calibration/labware/epic/__tests__/fetchLabwareCalibrationsEpic.test.js @@ -8,7 +8,7 @@ import * as Actions from '../../actions' import { labwareCalibrationEpic } from '..' const makeTriggerActionAllCalibrations = robotName => - Actions.fetchAllLabwareCalibrations(robotName) + Actions.fetchLabwareCalibrations(robotName) describe('fetch labware calibration epics', () => { afterEach(() => { @@ -32,66 +32,6 @@ describe('fetch labware calibration epics', () => { expect(mocks.fetchRobotApi).toHaveBeenCalledWith(mocks.robot, { method: 'GET', path: '/labware/calibrations', - query: { loadName: null, namespace: null, version: null }, - }) - }) - }) - - it('calls GET /labware/calibrations with all possible queries', () => { - const mocks = setupEpicTestMocks(robotName => { - return Actions.fetchAllLabwareCalibrations( - robotName, - 'my_cute_labware', - 'cutelabwares', - 2 - ) - }, Fixtures.mockFetchLabwareCalibrationSuccess) - - runEpicTest(mocks, ({ hot, expectObservable, flush }) => { - const action$ = hot('--a', { a: mocks.action }) - const state$ = hot('s-s', { s: mocks.state }) - const output$ = labwareCalibrationEpic(action$, state$) - - expectObservable(output$) - flush() - - expect(mocks.fetchRobotApi).toHaveBeenCalledWith(mocks.robot, { - method: 'GET', - path: '/labware/calibrations', - query: { - loadName: 'my_cute_labware', - namespace: 'cutelabwares', - version: 2, - }, - }) - }) - }) - it('calls GET /labware/calibrations with only some queries', () => { - const mocks = setupEpicTestMocks(robotName => { - return Actions.fetchAllLabwareCalibrations( - robotName, - null, - 'cutelabwares', - 2 - ) - }, Fixtures.mockFetchLabwareCalibrationSuccess) - - runEpicTest(mocks, ({ hot, expectObservable, flush }) => { - const action$ = hot('--a', { a: mocks.action }) - const state$ = hot('s-s', { s: mocks.state }) - const output$ = labwareCalibrationEpic(action$, state$) - - expectObservable(output$) - flush() - - expect(mocks.fetchRobotApi).toHaveBeenCalledWith(mocks.robot, { - method: 'GET', - path: '/labware/calibrations', - query: { - loadName: null, - namespace: 'cutelabwares', - version: 2, - }, }) }) }) diff --git a/app/src/calibration/labware/epic/fetchAllLabwareCalibrationEpic.js b/app/src/calibration/labware/epic/fetchLabwareCalibrationsEpic.js similarity index 70% rename from app/src/calibration/labware/epic/fetchAllLabwareCalibrationEpic.js rename to app/src/calibration/labware/epic/fetchLabwareCalibrationsEpic.js index 7fcc8da1249..19afdd1ff1d 100644 --- a/app/src/calibration/labware/epic/fetchAllLabwareCalibrationEpic.js +++ b/app/src/calibration/labware/epic/fetchLabwareCalibrationsEpic.js @@ -11,19 +11,14 @@ import type { ResponseToActionMapper, } from '../../../robot-api/operators' import type { Epic } from '../../../types' -import type { FetchLabwareCalibrationAction } from '../types' +import type { FetchLabwareCalibrationsAction } from '../types' -const mapActionToRequest: ActionToRequestMapper = action => { - const { robotName, ...payloadWithoutRobot } = action.payload +const mapActionToRequest: ActionToRequestMapper = action => ({ + method: GET, + path: Constants.LABWARE_CALIBRATION_PATH, +}) - return { - method: GET, - path: Constants.LABWARE_CALIBRATION_PATH, - query: payloadWithoutRobot, - } -} - -const mapResponseToAction: ResponseToActionMapper = ( +const mapResponseToAction: ResponseToActionMapper = ( response, originalAction ) => { @@ -34,9 +29,9 @@ const mapResponseToAction: ResponseToActionMapper : Actions.fetchLabwareCalibrationsFailure(host.name, body, meta) } -export const fetchAllLabwareCalibrationEpic: Epic = (action$, state$) => { +export const fetchLabwareCalibrationsEpic: Epic = (action$, state$) => { return action$.pipe( - ofType(Constants.FETCH_ALL_LABWARE_CALIBRATIONS), + ofType(Constants.FETCH_LABWARE_CALIBRATIONS), mapToRobotApiRequest( state$, a => a.payload.robotName, diff --git a/app/src/calibration/labware/epic/index.js b/app/src/calibration/labware/epic/index.js index 9951e6ef2ac..58d87584f6c 100644 --- a/app/src/calibration/labware/epic/index.js +++ b/app/src/calibration/labware/epic/index.js @@ -1,10 +1,10 @@ // @flow import { combineEpics } from 'redux-observable' -import { fetchAllLabwareCalibrationEpic } from './fetchAllLabwareCalibrationEpic' +import { fetchLabwareCalibrationsEpic } from './fetchLabwareCalibrationsEpic' import type { Epic } from '../../../types' export const labwareCalibrationEpic: Epic = combineEpics( - fetchAllLabwareCalibrationEpic + fetchLabwareCalibrationsEpic ) diff --git a/app/src/calibration/labware/selectors.js b/app/src/calibration/labware/selectors.js index f91d68e3d07..3326082f974 100644 --- a/app/src/calibration/labware/selectors.js +++ b/app/src/calibration/labware/selectors.js @@ -1,85 +1,107 @@ // @flow import { createSelector } from 'reselect' +import head from 'lodash/head' +import isEqual from 'lodash/isEqual' +import round from 'lodash/round' +import uniqWith from 'lodash/uniqWith' -import type { State } from '../../types' -import type { LabwareCalibrationObjects } from './../types' -import type { LabwareWithCalibration } from './types' import { getLabwareDisplayName, getModuleDisplayName, } from '@opentrons/shared-data' -import filter from 'lodash/filter' -import countBy from 'lodash/countBy' -import keyBy from 'lodash/keyBy' - import { selectors as robotSelectors } from '../../robot' -export const getListOfLabwareCalibrations = ( +import type { LabwareDefinition2, ModuleModel } from '@opentrons/shared-data' +import type { State } from '../../types' +import type { LabwareCalibrationModel } from './../types' +import type { LabwareSummary } from './types' + +export const getLabwareCalibrations = ( state: State, robotName: string -): Array | null => { - return state.calibration[robotName]?.labwareCalibration?.data ?? null +): Array => { + return state.calibration[robotName]?.labwareCalibrations?.data ?? [] } -// Note: due to some circular dependencies, this selector needs -// to live in calibration/labware though it should actually be -// a part of the protocol/ selectors -export const associateLabwareWithCalibration: ( +type BaseProtocolLabware = {| + definition: LabwareDefinition2 | null, + loadName: string, + namespace: string | null, + version: number | null, + parent: ModuleModel | null, + legacy: boolean, +|} + +// TODO(mc, 2020-07-27): this selector should move to a protocol-focused module +// when we don't have to rely on RPC-state selectors for protocol equipment info +// NOTE(mc, 2020-07-27): due to how these endpo +export const getProtocolLabwareList: ( state: State, robotName: string -) => Array = createSelector< - State, - string, - Array, - _, - _, - _ ->( - (state: State) => robotSelectors.getLabware(state), - robotSelectors.getModulesBySlot, - getListOfLabwareCalibrations, - (labware, modulesBySlot, labwareCalibrations) => { - const updatedLabwareType = [] - labware.map(lw => { - const moduleName = modulesBySlot[lw.slot]?.model ?? '' - const newDataModel = { ...lw } - newDataModel.type = lw.type + moduleName - updatedLabwareType.push(newDataModel) - }) +) => Array = createSelector( + (state, robotName) => robotSelectors.getLabware(state), + (state, robotName) => robotSelectors.getModulesBySlot(state), + getLabwareCalibrations, + (protocolLabware, modulesBySlot, calibrations) => { + const baseLabwareList: $ReadOnlyArray = protocolLabware.map( + lw => ({ + definition: lw.definition, + loadName: lw.definition?.parameters.loadName ?? lw.type, + namespace: lw.definition?.namespace ?? null, + version: lw.definition?.version ?? null, + parent: modulesBySlot[lw.slot]?.model ?? null, + legacy: lw.isLegacy, + }) + ) - const labwareCount = countBy(updatedLabwareType, 'type') - const calibrations = filter(labwareCalibrations, function(l) { - return Object.keys(labwareCount).includes(l?.attributes.loadName) - }) + const uniqueLabware = uniqWith( + baseLabwareList, + (labwareA, labwareB) => { + const { definition: _defA, ...labwareIdentityA } = labwareA + const { definition: _defB, ...labwareIdentityB } = labwareB + return isEqual(labwareIdentityA, labwareIdentityB) + } + ) - const calibrationLoadNamesMap = keyBy(calibrations, function( - labwareObject - ) { - const loadName = labwareObject?.attributes.loadName ?? '' - const parent = labwareObject?.attributes.parent ?? '' - return loadName + parent - }) + return uniqueLabware.map(lw => { + const { definition: def, loadName, namespace, version, parent } = lw + const displayName = def ? getLabwareDisplayName(def) : loadName + const parentDisplayName = parent ? getModuleDisplayName(parent) : null - const labwareDisplayNames = [] - updatedLabwareType.map(lw => { - const moduleName = modulesBySlot[lw.slot]?.model ?? '' - const parentName = (moduleName && getModuleDisplayName(moduleName)) || '' - const data = - calibrationLoadNamesMap[lw.type]?.attributes.calibrationData.offset - .value - const calibrationData = data - ? { x: data[0], y: data[1], z: data[2] } - : null - const displayName = - (lw.definition && getLabwareDisplayName(lw.definition)) || '' - return labwareDisplayNames.push({ - display: displayName, - quantity: labwareCount[lw.type], - parent: parentName, - calibration: calibrationData, - }) + const quantity = baseLabwareList.filter(baseLw => { + return ( + baseLw.loadName === loadName && + baseLw.namespace === namespace && + baseLw.version === version + ) + }).length + + const calData = calibrations + .filter(({ attributes }) => { + return ( + attributes.loadName === loadName && + attributes.namespace === namespace && + attributes.version === version && + (!parent || attributes.parent === parent) + ) + }) + .map(({ attributes }) => { + const calVector = attributes.calibrationData.offset.value + return { + x: round(calVector[0], 1), + y: round(calVector[1], 1), + z: round(calVector[2], 1), + } + }) + + return { + displayName, + parentDisplayName, + quantity, + calibration: head(calData) ?? null, + legacy: lw.legacy, + } }) - return labwareDisplayNames } ) diff --git a/app/src/calibration/labware/types.js b/app/src/calibration/labware/types.js index 5aa70b2ecbf..a72bc672318 100644 --- a/app/src/calibration/labware/types.js +++ b/app/src/calibration/labware/types.js @@ -6,49 +6,45 @@ import type { } from '../../robot-api/types' import typeof { - FETCH_ALL_LABWARE_CALIBRATIONS, - FETCH_LABWARE_CALIBRATION_SUCCESS, - FETCH_LABWARE_CALIBRATION_FAILURE, + FETCH_LABWARE_CALIBRATIONS, + FETCH_LABWARE_CALIBRATIONS_SUCCESS, + FETCH_LABWARE_CALIBRATIONS_FAILURE, } from './constants' import type { AllLabwareCalibrations } from './../api-types' -export type FetchLabwareCalibrationAction = {| - type: FETCH_ALL_LABWARE_CALIBRATIONS, - payload: {| - robotName: string, - loadName: string | null, - version: number | null, - namespace: string | null, - |}, +export type FetchLabwareCalibrationsAction = {| + type: FETCH_LABWARE_CALIBRATIONS, + payload: {| robotName: string |}, meta: RobotApiRequestMeta, |} -export type FetchAllLabwareCalibrationSuccessAction = {| - type: FETCH_LABWARE_CALIBRATION_SUCCESS, +export type FetchLabwareCalibrationsSuccessAction = {| + type: FETCH_LABWARE_CALIBRATIONS_SUCCESS, payload: {| robotName: string, - labwareCalibration: AllLabwareCalibrations, + labwareCalibrations: AllLabwareCalibrations, |}, meta: RobotApiRequestMeta, |} -export type FetchLabwareCalibrationFailureAction = {| - type: FETCH_LABWARE_CALIBRATION_FAILURE, +export type FetchLabwareCalibrationsFailureAction = {| + type: FETCH_LABWARE_CALIBRATIONS_FAILURE, payload: {| robotName: string, error: RobotApiErrorResponse |}, meta: RobotApiRequestMeta, |} // selector types -export type LabwareWithCalibration = {| - parent: string, +export type LabwareSummary = {| + displayName: string, + parentDisplayName: string | null, quantity: number, - display: string, calibration: {| x: number, y: number, z: number |} | null, + legacy: boolean, |} export type LawareCalibrationAction = - | FetchLabwareCalibrationAction - | FetchAllLabwareCalibrationSuccessAction - | FetchLabwareCalibrationFailureAction + | FetchLabwareCalibrationsAction + | FetchLabwareCalibrationsSuccessAction + | FetchLabwareCalibrationsFailureAction diff --git a/app/src/calibration/reducer.js b/app/src/calibration/reducer.js index c970eb48769..209a48da4f4 100644 --- a/app/src/calibration/reducer.js +++ b/app/src/calibration/reducer.js @@ -3,10 +3,15 @@ import * as Constants from './constants' import * as labware from './labware' import type { Action } from '../types' -import type { CalibrationState } from './types' +import type { CalibrationState, PerRobotCalibrationState } from './types' const INITIAL_STATE: CalibrationState = {} +const INITIAL_PER_ROBOT_STATE: PerRobotCalibrationState = { + calibrationStatus: null, + labwareCalibrations: null, +} + export function calibrationReducer( state: CalibrationState = INITIAL_STATE, action: Action @@ -14,23 +19,16 @@ export function calibrationReducer( switch (action.type) { case Constants.FETCH_CALIBRATION_STATUS_SUCCESS: { const { robotName, calibrationStatus } = action.payload - return { - ...state, - [robotName]: { - ...(state[robotName] ?? {}), - calibrationStatus, - }, - } + const robotState = state[robotName] ?? INITIAL_PER_ROBOT_STATE + + return { ...state, [robotName]: { ...robotState, calibrationStatus } } } - case labware.FETCH_LABWARE_CALIBRATION_SUCCESS: { - const { robotName, labwareCalibration } = action.payload - return { - ...state, - [robotName]: { - ...(state[robotName] ?? {}), - labwareCalibration, - }, - } + + case labware.FETCH_LABWARE_CALIBRATIONS_SUCCESS: { + const { robotName, labwareCalibrations } = action.payload + const robotState = state[robotName] ?? INITIAL_PER_ROBOT_STATE + + return { ...state, [robotName]: { ...robotState, labwareCalibrations } } } } return state diff --git a/app/src/calibration/selectors.js b/app/src/calibration/selectors.js index fde1b904fb0..bd7bb25fa19 100644 --- a/app/src/calibration/selectors.js +++ b/app/src/calibration/selectors.js @@ -14,8 +14,5 @@ export const getDeckCalibrationStatus = ( state: State, robotName: string ): DeckCalibrationStatus | null => { - return ( - state.calibration[robotName]?.calibrationStatus.deckCalibration.status ?? - null - ) + return getCalibrationStatus(state, robotName)?.deckCalibration.status ?? null } diff --git a/app/src/calibration/types.js b/app/src/calibration/types.js index 95e32d5b37a..77b4e53eda7 100644 --- a/app/src/calibration/types.js +++ b/app/src/calibration/types.js @@ -15,7 +15,8 @@ import typeof { FETCH_CALIBRATION_STATUS_FAILURE, } from './constants' -export * from './api-types' +export type * from './api-types' +export type * from './labware/types' export type FetchCalibrationStatusAction = {| type: FETCH_CALIBRATION_STATUS, @@ -45,8 +46,8 @@ export type CalibrationAction = | LawareCalibrationAction export type PerRobotCalibrationState = $ReadOnly<{| - calibrationStatus: CalibrationStatus, - labwareCalibration: ?AllLabwareCalibrations, + calibrationStatus: CalibrationStatus | null, + labwareCalibrations: AllLabwareCalibrations | null, |}> export type CalibrationState = $ReadOnly<{ diff --git a/app/src/components/FileInfo/Continue.js b/app/src/components/FileInfo/Continue.js index 03b068abf16..b46731c7242 100644 --- a/app/src/components/FileInfo/Continue.js +++ b/app/src/components/FileInfo/Continue.js @@ -1,39 +1,48 @@ // @flow import * as React from 'react' +import cx from 'classnames' import { useSelector } from 'react-redux' import { Link } from 'react-router-dom' import { getCalibrateLocation } from '../../nav' + import { - PrimaryButton, useHoverTooltip, + Box, + PrimaryBtn, + Text, Tooltip, - TOOLTIP_AUTO, FONT_SIZE_CAPTION, - Text, + SPACING_1, + SIZE_5, + TEXT_ALIGN_RIGHT, + TOOLTIP_LEFT, } from '@opentrons/components' -import styles from './styles.css' + +// TODO(mc, 2020-07-27): i18n +const PROCEED_TO_CALIBRATE = 'Proceed to Calibrate' +const VERIFY_CALIBRATIONS = 'Verify pipette and labware calibrations' export function Continue(): React.Node { - const buttonText = 'Proceed to Calibrate' - const primarySublabelText = 'Verify pipette and labware calibrations' const { path, disabledReason } = useSelector(getCalibrateLocation) const [targetProps, tooltipProps] = useHoverTooltip({ - placement: TOOLTIP_AUTO, + placement: TOOLTIP_LEFT, }) return ( -
- + - {buttonText} - + {PROCEED_TO_CALIBRATE} + {disabledReason && {disabledReason}} - {primarySublabelText} -
+ {VERIFY_CALIBRATIONS} + ) } diff --git a/app/src/components/FileInfo/ProtocolLabwareCard.js b/app/src/components/FileInfo/ProtocolLabwareCard.js index 51ee5c5ea02..5408056ad8f 100644 --- a/app/src/components/FileInfo/ProtocolLabwareCard.js +++ b/app/src/components/FileInfo/ProtocolLabwareCard.js @@ -2,11 +2,13 @@ // setup labware component import * as React from 'react' import { useSelector, useDispatch } from 'react-redux' -import round from 'lodash/round' import { InfoSection } from './InfoSection' import { ProtocolLabwareList } from './ProtocolLabwareList' -import * as labwareFunctions from '../../calibration' +import { + fetchLabwareCalibrations, + getProtocolLabwareList, +} from '../../calibration' import type { State, Dispatch } from '../../types' @@ -14,42 +16,25 @@ export type ProtocolLabwareProps = {| robotName: string, |} -const TITLE = 'Required Labware' +// TODO(mc, 2020-07-27): i18n +const REQUIRED_LABWARE = 'Required Labware' -export function ProtocolLabwareCard({ - robotName, -}: ProtocolLabwareProps): React.Node { +export function ProtocolLabwareCard(props: ProtocolLabwareProps): React.Node { + const { robotName } = props const dispatch = useDispatch() + const labwareList = useSelector((state: State) => { + return getProtocolLabwareList(state, robotName) + }) + React.useEffect(() => { - dispatch(labwareFunctions.fetchAllLabwareCalibrations(robotName)) + dispatch(fetchLabwareCalibrations(robotName)) }, [dispatch, robotName]) - const labwareWithCalibration = useSelector((state: State) => - labwareFunctions.associateLabwareWithCalibration(state, robotName) - ) - if (labwareWithCalibration.length === 0) return null - - const labwareToParentMap = [] - labwareWithCalibration.map(labwareInfo => { - const offset = labwareInfo.calibration - let calibrationData = null - if (offset) { - const X = parseFloat(round(offset.x, 1)).toFixed(1) - const Y = parseFloat(round(offset.y, 1)).toFixed(1) - const Z = parseFloat(round(offset.z, 1)).toFixed(1) - calibrationData = { x: X, y: Y, z: Z } - } - - labwareToParentMap.push({ - parent: labwareInfo.parent, - quantity: labwareInfo.quantity, - display: labwareInfo.display, - calibration: calibrationData, - }) - }) + + if (labwareList.length === 0) return null return ( - - + + ) } diff --git a/app/src/components/FileInfo/ProtocolLabwareList.js b/app/src/components/FileInfo/ProtocolLabwareList.js index 9fe4d0f5e96..beac467d940 100644 --- a/app/src/components/FileInfo/ProtocolLabwareList.js +++ b/app/src/components/FileInfo/ProtocolLabwareList.js @@ -2,109 +2,111 @@ import * as React from 'react' import { + useHoverTooltip, + Box, Flex, + Icon, Text, Tooltip, - useHoverTooltip, + ALIGN_FLEX_END, FONT_SIZE_BODY_1, - FONT_WEIGHT_REGULAR, FONT_WEIGHT_SEMIBOLD, - C_DARK_GRAY, - TOOLTIP_TOP, - DIRECTION_ROW, - JUSTIFY_SPACE_BETWEEN, + LINE_HEIGHT_COPY, SIZE_1, - ALIGN_CENTER, + SPACING_2, + SPACING_3, + SPACING_AUTO, + TOOLTIP_TOP_START, } from '@opentrons/components' -import { css } from 'styled-components' -export type LoadNameMapProps = {| - parent: string, - quantity: number, - display: string, - calibration: {| x: string, y: string, z: string |} | null, -|} +import type { LabwareSummary } from '../../calibration/types' + +// TODO(mc, 2020-07-27): i18n +const TYPE = 'Type' +const QUANTITY = 'Quantity' +const CALIBRATION_DATA = 'Calibration Data' +const LEGACY_DEFINITION = 'Legacy definition' +const NOT_CALIBRATED = 'Not yet calibrated' +const CALIBRATION_DESCRIPTION = 'Calibrated offset from labware origin point' export type ProtocolLabwareListProps = {| - loadNameMap: Array, + labware: Array, |} -export function ProtocolLabwareList({ - loadNameMap, -}: ProtocolLabwareListProps): React.Node { - const [targetProps, tooltipProps] = useHoverTooltip({ - placement: TOOLTIP_TOP, +const TYPE_COL_STYLE = { marginRight: SPACING_AUTO } +const QUANTITY_COL_STYLE = { width: '12.5%', marginX: SPACING_3 } +const CAL_DATA_COL_STYLE = { width: '25%' } + +const renderCalValue = (axis: string, value: number): React.Node => ( + <> + + {axis.toUpperCase()} + {' '} + + {value.toFixed(1)} + + +) + +export function ProtocolLabwareList( + props: ProtocolLabwareListProps +): React.Node { + const { labware } = props + const [calDescTooltipTargetProps, calDescTooltipProps] = useHoverTooltip({ + placement: TOOLTIP_TOP_START, }) - const TOOL_TIP_MESSAGE = 'calibrated offset from labware origin point' - const LABWARE_TYPE = 'Type' - const LABWARE_QUANTITY = 'Quantity' - const CALIBRATION_DATA = 'Calibration Data' - const NOT_CALIBRATED = 'Not yet calibrated' return ( - - - - - - - - - {loadNameMap.map((labwareObj, index) => { - return ( - - - - - - ) - })} - -
{LABWARE_TYPE}{LABWARE_QUANTITY} -
- {CALIBRATION_DATA} - {TOOL_TIP_MESSAGE} -
-
-
- {labwareObj.parent} - {labwareObj.display} -
-
{`x${labwareObj.quantity}`} - {labwareObj.calibration ? ( - -
X
-
{labwareObj.calibration.x}
-
Y
-
{labwareObj.calibration.y}
-
Z
-
{labwareObj.calibration.z}
-
- ) : ( - - {NOT_CALIBRATED} - - )} -
-
+ + + {TYPE} + {QUANTITY} + + {CALIBRATION_DATA} + + {CALIBRATION_DESCRIPTION} + + +
    + {labware.map((lw, index) => { + const { + displayName, + parentDisplayName, + quantity, + calibration, + legacy, + } = lw + + return ( + + + {parentDisplayName && {parentDisplayName}} + {displayName} + + x {quantity} + + {calibration !== null ? ( + <> + {renderCalValue('x', calibration.x)} + {renderCalValue('y', calibration.y)} + {renderCalValue('z', calibration.z)} + + ) : legacy ? ( + LEGACY_DEFINITION + ) : ( + NOT_CALIBRATED + )} + + + ) + })} +
+
) } diff --git a/app/src/components/FileInfo/__tests__/continue.test.js b/app/src/components/FileInfo/__tests__/Continue.test.js similarity index 68% rename from app/src/components/FileInfo/__tests__/continue.test.js rename to app/src/components/FileInfo/__tests__/Continue.test.js index 7e0b3dda96c..3cb67952f9f 100644 --- a/app/src/components/FileInfo/__tests__/continue.test.js +++ b/app/src/components/FileInfo/__tests__/Continue.test.js @@ -1,19 +1,20 @@ // @flow import * as React from 'react' import { Provider } from 'react-redux' +import { StaticRouter } from 'react-router-dom' import { mount } from 'enzyme' import noop from 'lodash/noop' import * as navigation from '../../../nav' -import { PrimaryButton, OutlineButton, Tooltip } from '@opentrons/components' +import { Tooltip } from '@opentrons/components' import { Continue } from '../Continue' import type { State } from '../../../types' -import { MemoryRouter } from 'react-router-dom' jest.mock('../../../nav') const MOCK_STATE: State = ({ mockState: true }: any) + const MOCK_STORE = { getState: () => MOCK_STATE, dispatch: noop, @@ -32,21 +33,20 @@ function stubSelector(mock: JestMockFn<[State], R>, rVal: R) { }) } -const mockCalPath = '/path/to/cal' - describe('Continue to run or calibration button component', () => { const render = (labwareCalibrated: boolean = false) => { return mount( - + - + ) } - const CALIBRATE_SELECTOR = { + + const CALIBRATE_LOCATION_ENABLED = { id: 'calibrate', - path: mockCalPath, + path: '/calibrate', title: 'CALIBRATE', iconName: 'ot-calibrate', disabledReason: null, @@ -54,39 +54,41 @@ describe('Continue to run or calibration button component', () => { const CALIBRATE_SELECTOR_DISABLED = { id: 'calibrate', - path: mockCalPath, + path: '/calibrate', title: 'CALIBRATE', iconName: 'ot-calibrate', disabledReason: 'check your toolbox!', } beforeEach(() => { - stubSelector(getCalibrateLocation, CALIBRATE_SELECTOR) + stubSelector(getCalibrateLocation, CALIBRATE_LOCATION_ENABLED) }) afterEach(() => { jest.resetAllMocks() }) - it('Default button renders to continue to labware when not all labware is calibrated', () => { + it('renders a link to /calibrate when calibrate page is enabled', () => { const wrapper = render() - const button = wrapper.find(PrimaryButton) - const secondarybutton = wrapper.find(OutlineButton) + const link = wrapper.find('a') const tooltip = wrapper.find(Tooltip) expect(tooltip.exists()).toEqual(false) - expect(button.children().text()).toEqual('Proceed to Calibrate') - expect(secondarybutton.exists()).toEqual(false) - expect(button.props().to).toEqual(mockCalPath) + expect(link.children().text()).toEqual('Proceed to Calibrate') + expect(link.prop('href')).toBe('/calibrate') }) - it('Test tool tip when disabled reason given', () => { + it('renders a tooltip and a noop link when calibrate page is disabled', () => { stubSelector(getCalibrateLocation, CALIBRATE_SELECTOR_DISABLED) const wrapper = render() + const link = wrapper.find('a') const tooltip = wrapper.find(Tooltip) + expect(tooltip.exists()).toEqual(true) expect(tooltip.prop('children')).toBe( CALIBRATE_SELECTOR_DISABLED.disabledReason ) + expect(link.prop('className')).toContain('disabled') + expect(link.prop('href')).toBe('/upload/file-info') }) }) diff --git a/app/src/components/FileInfo/__tests__/index.test.js b/app/src/components/FileInfo/__tests__/FileInfo.test.js similarity index 92% rename from app/src/components/FileInfo/__tests__/index.test.js rename to app/src/components/FileInfo/__tests__/FileInfo.test.js index 15566312a62..f3a150b10ec 100644 --- a/app/src/components/FileInfo/__tests__/index.test.js +++ b/app/src/components/FileInfo/__tests__/FileInfo.test.js @@ -30,7 +30,7 @@ describe('File info Component', () => { expect(wrapper.find(ProtocolPipettesCard).exists()).toEqual(true) expect(wrapper.find(ProtocolModulesCard).exists()).toEqual(true) expect(wrapper.find(Continue).exists()).toEqual(true) - expect(labwareCard.props().robotName).toEqual('robot-name') + expect(labwareCard.prop('robotName')).toEqual('robot-name') }) it('An error renders when an upload error is given', () => { @@ -46,6 +46,6 @@ describe('File info Component', () => { // button should not render when upload error occurs expect(button.exists()).toEqual(false) expect(wrapper.exists(UploadError)).toEqual(true) - expect(uploadError.props().uploadError.message).toEqual('Oh No!') + expect(uploadError.prop('uploadError')).toEqual({ message: 'Oh No!' }) }) }) diff --git a/app/src/components/FileInfo/__tests__/ProtocolLabwareCard.test.js b/app/src/components/FileInfo/__tests__/ProtocolLabwareCard.test.js new file mode 100644 index 00000000000..d657bb03b58 --- /dev/null +++ b/app/src/components/FileInfo/__tests__/ProtocolLabwareCard.test.js @@ -0,0 +1,82 @@ +// @flow +import * as React from 'react' +import { mount } from 'enzyme' +import { Provider } from 'react-redux' +import noop from 'lodash/noop' + +import * as Calibration from '../../../calibration' +import { ProtocolLabwareCard } from '../ProtocolLabwareCard' +import { ProtocolLabwareList } from '../ProtocolLabwareList' +import { InfoSection } from '../InfoSection' + +import type { State } from '../../../types' + +jest.mock('../../../calibration') + +const MOCK_STATE: State = ({ mockState: true }: any) + +const MOCK_STORE = { + getState: () => MOCK_STATE, + dispatch: noop, + subscribe: noop, +} +const ROBOT_NAME = 'robotName' + +const getProtocolLabwareList: JestMockFn< + [State, string], + $Call +> = Calibration.getProtocolLabwareList + +function stubSelector(mock: JestMockFn<[State, string], R>, rVal: R) { + mock.mockImplementation((state, robotName) => { + expect(state).toBe(MOCK_STATE) + expect(robotName).toBe(ROBOT_NAME) + return rVal + }) +} + +describe('ProtocolLabwareCard', () => { + const render = () => { + return mount(, { + wrappingComponent: Provider, + wrappingComponentProps: { store: MOCK_STORE }, + }) + } + + const EMPTY_LABWARE = [] + const FULL_LABWARE = [ + { + quantity: 2, + calibration: null, + parentDisplayName: null, + displayName: 'LABWARE DISPLAY NAME', + legacy: false, + }, + ] + + beforeEach(() => { + stubSelector(getProtocolLabwareList, FULL_LABWARE) + }) + + afterEach(() => { + jest.resetAllMocks() + }) + + it('renders nothing when no labware exists in the protocol', () => { + stubSelector(getProtocolLabwareList, EMPTY_LABWARE) + const wrapper = render() + expect(wrapper).toEqual({}) + }) + + it('renders an info section with a title if there are labware', () => { + const wrapper = render() + const infoSection = wrapper.find(InfoSection) + expect(infoSection.prop('title')).toEqual('Required Labware') + }) + + it('passes labware list from selector to ProtocolLabwareList', () => { + const wrapper = render() + const labwareList = wrapper.find(ProtocolLabwareList) + expect(labwareList.prop('labware')).toEqual(FULL_LABWARE) + }) +}) diff --git a/app/src/components/FileInfo/__tests__/ProtocolLabwareList.test.js b/app/src/components/FileInfo/__tests__/ProtocolLabwareList.test.js new file mode 100644 index 00000000000..0ca41dc40b2 --- /dev/null +++ b/app/src/components/FileInfo/__tests__/ProtocolLabwareList.test.js @@ -0,0 +1,101 @@ +// @flow +import * as React from 'react' +import { mount } from 'enzyme' + +import { Icon, Tooltip, Text } from '@opentrons/components' +import { ProtocolLabwareList } from '../ProtocolLabwareList' + +const LABWARE = [ + { + displayName: 'Opentrons Labware', + parentDisplayName: null, + quantity: 2, + calibration: null, + legacy: false, + }, + { + displayName: 'Other Opentrons Labware', + parentDisplayName: 'MODULE GEN1', + quantity: 1, + calibration: { x: 1.2, y: 3, z: 4.5 }, + legacy: false, + }, + { + displayName: 'V1 Labware', + parentDisplayName: null, + quantity: 3, + calibration: null, + legacy: true, + }, +] + +describe('ProtocolLabwareList Component', () => { + const render = () => { + return mount() + } + + it('renders a list with one item per labware', () => { + const wrapper = render() + const list = wrapper.find('ul') + const items = list.find('li') + expect(items).toHaveLength(LABWARE.length) + }) + + it('renders a "table header"', () => { + const wrapper = render() + // cast Tooltip to any so we can use it in the matcher below without props + const Tt: any = Tooltip + + /* eslint-disable react/jsx-key */ + expect( + wrapper.containsAllMatchingElements([ + Type, + Quantity, + Calibration Data, + , + Calibrated offset from labware origin point, + ]) + ).toBe(true) + /* eslint-enable react/jsx-key */ + }) + + it('renders labware props', () => { + const wrapper = render() + + const item0 = wrapper.find('li').at(0) + const item1 = wrapper.find('li').at(1) + const item2 = wrapper.find('li').at(2) + + /* eslint-disable react/jsx-key */ + expect( + item0.containsAllMatchingElements([ + Opentrons Labware, + x 2, + Not yet calibrated, + ]) + ).toBe(true) + + expect( + item1.containsAllMatchingElements([ + MODULE GEN1, + Other Opentrons Labware, + x 1, + X, + 1.2, + Y, + 3.0, + Z, + 4.5, + ]) + ).toBe(true) + + expect( + item2.containsAllMatchingElements([ + V1 Labware, + x 3, + Legacy definition, + ]) + ).toBe(true) + /* eslint-enable react/jsx-key */ + }) +}) diff --git a/app/src/components/FileInfo/__tests__/labwarecard.test.js b/app/src/components/FileInfo/__tests__/labwarecard.test.js deleted file mode 100644 index a4a6ff8bf7d..00000000000 --- a/app/src/components/FileInfo/__tests__/labwarecard.test.js +++ /dev/null @@ -1,84 +0,0 @@ -// @flow -import * as React from 'react' -import { mount } from 'enzyme' -import { Provider } from 'react-redux' -import noop from 'lodash/noop' - -import type { State } from '../../../types' - -import { ProtocolLabwareCard } from '../ProtocolLabwareCard' -import { ProtocolLabwareList } from '../ProtocolLabwareList' -import * as labwareSelect from '../../../calibration' - -jest.mock('../../../calibration') - -const MOCK_STATE: State = ({ mockState: true }: any) -const MOCK_STORE = { - getState: () => MOCK_STATE, - dispatch: noop, - subscribe: noop, -} -const ROBOT_NAME = 'randomRobot' - -const associateLabwareWithCalibration: JestMockFn< - [State, string], - $Call -> = labwareSelect.associateLabwareWithCalibration - -function stubSelector(mock: JestMockFn<[State, string], R>, rVal: R) { - mock.mockImplementation(state => { - expect(state).toBe(MOCK_STATE) - return rVal - }) -} - -describe('Protocol Labware Card Component', () => { - const render = (robotName = ROBOT_NAME) => { - return mount( - - - - ) - } - - const EMPTY_LABWARE = [] - const FULL_LABWARE = [ - { - quantity: 2, - calibration: null, - parent: '', - display: 'LABWARE DISPLAY NAME', - }, - ] - - beforeEach(() => { - stubSelector(associateLabwareWithCalibration, FULL_LABWARE) - }) - - afterEach(() => { - jest.resetAllMocks() - }) - - it('renders nothing when no labware exists in the protocol', () => { - stubSelector(associateLabwareWithCalibration, EMPTY_LABWARE) - const wrapper = render() - expect(wrapper).toEqual({}) - }) - - it('passes in corectly formatted quantity and calibration to labware list', () => { - const wrapper = render() - const labwareList = wrapper.find(ProtocolLabwareList) - const props = labwareList.props() - const expected = { - parent: '', - quantity: 2, - display: 'LABWARE DISPLAY NAME', - calibration: null, - } - expect(labwareList.exists()).toEqual(true) - expect(props.loadNameMap).toEqual([expected]) - const tbody = labwareList.find('tbody') - expect(tbody).toHaveLength(1) - expect(tbody.find('tr').find('td').length).toEqual(3) - }) -}) diff --git a/app/src/components/FileInfo/__tests__/labwarelist.test.js b/app/src/components/FileInfo/__tests__/labwarelist.test.js deleted file mode 100644 index 3522f70549b..00000000000 --- a/app/src/components/FileInfo/__tests__/labwarelist.test.js +++ /dev/null @@ -1,76 +0,0 @@ -// @flow -import * as React from 'react' -import { mount } from 'enzyme' - -import { Tooltip } from '@opentrons/components' -import { ProtocolLabwareList } from '../ProtocolLabwareList' -import type { LoadNameMapProps } from '../ProtocolLabwareList' - -describe('Protocol Labware List Component', () => { - const render = loadNameMap => { - return mount() - } - - it('All three sections render, with tool tip', () => { - const props: Array = [ - { - parent: '', - quantity: 2, - display: 'Opentrons Labware', - calibration: null, - }, - { - parent: 'MODULE GEN1', - quantity: 1, - display: 'Other Opentrons Labware', - calibration: { x: '1', y: '1', z: '1' }, - }, - ] - const wrapper = render(props) - const table = wrapper.find('tbody') - const headers = table.find('th') - const rows = table.find('tr') - const tooltip = table.find(Tooltip) - const titleList = ['Type', 'Quantity', 'Calibration Data'] - - expect(tooltip.exists()).toEqual(true) - expect(rows.length).toEqual(3) - headers.forEach(section => expect(titleList).toContain(section.text())) - expect( - rows - .find('td') - .at(0) - .text() - ).toEqual('Opentrons Labware') - expect( - rows - .find('td') - .at(1) - .text() - ).toEqual('x2') - expect( - rows - .find('td') - .at(2) - .text() - ).toEqual('Not yet calibrated') - expect( - rows - .find('td') - .at(3) - .text() - ).toEqual('MODULE GEN1Other Opentrons Labware') - expect( - rows - .find('td') - .at(4) - .text() - ).toEqual('x1') - expect( - rows - .find('td') - .at(5) - .text() - ).toEqual('X1Y1Z1') - }) -}) diff --git a/app/src/components/FileInfo/index.js b/app/src/components/FileInfo/index.js index 354be77aa3e..a6763800085 100644 --- a/app/src/components/FileInfo/index.js +++ b/app/src/components/FileInfo/index.js @@ -2,13 +2,13 @@ import * as React from 'react' // TODO(mc, 2018-09-13): these aren't cards; rename +import { Box, OVERFLOW_AUTO, SPACING_4, SPACING_2 } from '@opentrons/components' import { InformationCard } from './InformationCard' import { ProtocolPipettesCard } from './ProtocolPipettesCard' import { ProtocolModulesCard } from './ProtocolModulesCard' import { ProtocolLabwareCard } from './ProtocolLabwareCard' import { Continue } from './Continue' import { UploadError } from '../UploadError' -import styles from './styles.css' import type { Robot } from '../../discovery/types' @@ -31,13 +31,17 @@ export function FileInfo(props: FileInfoProps): React.Node { } return ( -
+ {uploadError && } {sessionLoaded && !uploadError && } -
+ ) } diff --git a/app/src/components/FileInfo/styles.css b/app/src/components/FileInfo/styles.css index 38fb40a92ab..8cd96de6374 100644 --- a/app/src/components/FileInfo/styles.css +++ b/app/src/components/FileInfo/styles.css @@ -1,10 +1,5 @@ @import '@opentrons/components'; -.file_info_container { - overflow-y: auto; - padding: 0 1.5rem; -} - .info_section { width: 100%; padding-top: 1.25rem; @@ -90,16 +85,6 @@ } } -.continue { - text-align: right; -} - -.continue_button { - display: inline-block; - width: 16.5rem; - margin-bottom: 0.25rem; -} - .soft_warning { color: var(--c-med-gray); font-size: var(--fs-body-1); diff --git a/app/src/epic.js b/app/src/epic.js index 3ca15227daa..f94197589d5 100644 --- a/app/src/epic.js +++ b/app/src/epic.js @@ -16,7 +16,7 @@ import { shellEpic } from './shell/epic' import { alertsEpic } from './alerts/epic' import { systemInfoEpic } from './system-info/epic' import { sessionsEpic } from './sessions/epic' -import { calibrationEpic } from './calibration/' +import { calibrationEpic } from './calibration/epic' import type { Epic } from './types' diff --git a/app/src/protocol/selectors.js b/app/src/protocol/selectors.js index 61a58f8e0cb..3a0906a1224 100644 --- a/app/src/protocol/selectors.js +++ b/app/src/protocol/selectors.js @@ -1,8 +1,7 @@ // @flow -import { createSelector } from 'reselect' - import path from 'path' import startCase from 'lodash/startCase' +import { createSelector } from 'reselect' import { getter } from '@thi.ng/paths' import { getProtocolSchemaVersion } from '@opentrons/shared-data' import { fileIsJson } from './protocol-data' diff --git a/app/src/robot-api/__tests__/http.test.js b/app/src/robot-api/__tests__/http.test.js index 451cae96709..95684ae9f09 100644 --- a/app/src/robot-api/__tests__/http.test.js +++ b/app/src/robot-api/__tests__/http.test.js @@ -84,9 +84,14 @@ describe('robot-api http client', () => { const url = robotApiUrl(robot, { method: GET, path: '/health', - query: { fakeParam: '' }, + query: { + emptyParam: '', + nullParam: null, + voidParam: undefined, + falseParam: false, + }, }) - expect(url).toEqual(`http://127.0.0.1:${testPort}/health?`) + expect(url).toEqual(`http://127.0.0.1:${testPort}/health?falseParam=false`) }) it('can make a get request', () => { diff --git a/app/src/robot-api/types.js b/app/src/robot-api/types.js index d9db363e333..841c68c6e80 100644 --- a/app/src/robot-api/types.js +++ b/app/src/robot-api/types.js @@ -14,7 +14,7 @@ export type RobotApiRequestOptions = {| method: Method, path: string, body?: { ... }, - query?: { [param: string]: string | boolean | number | null }, + query?: { [param: string]: string | boolean | number | null | void, ... }, form?: FormData, |} diff --git a/components/src/primitives/Btn.js b/components/src/primitives/Btn.js index 9f21629111f..9af93a0494d 100644 --- a/components/src/primitives/Btn.js +++ b/components/src/primitives/Btn.js @@ -18,7 +18,8 @@ const BUTTON_BASE_STYLE = css` background-color: transparent; cursor: pointer; - &:disabled { + &:disabled, + &.disabled { cursor: default; } ` @@ -26,6 +27,7 @@ const BUTTON_BASE_STYLE = css` const BUTTON_VARIANT_STYLE = css` border-color: inherit; border-radius: ${Styles.BORDER_RADIUS_DEFAULT}; + display: ${Styles.DISPLAY_INLINE_BLOCK}; font-size: ${Styles.FONT_SIZE_BODY_2}; font-weight: ${Styles.FONT_WEIGHT_SEMIBOLD}; line-height: 1.4; @@ -33,6 +35,7 @@ const BUTTON_VARIANT_STYLE = css` padding-right: ${Styles.SPACING_4}; padding-top: ${Styles.SPACING_2}; padding-bottom: ${Styles.SPACING_2}; + text-align: ${Styles.TEXT_ALIGN_CENTER}; text-transform: ${Styles.TEXT_TRANSFORM_UPPERCASE}; ` @@ -75,7 +78,8 @@ export const PrimaryBtn: BtnComponent = styled(Btn)` background-color: ${Styles.C_MED_DARK_GRAY}; } - &:disabled { + &:disabled, + &.disabled { background-color: ${Styles.C_LIGHT_GRAY}; color: ${Styles.C_MED_GRAY}; box-shadow: none; @@ -103,7 +107,8 @@ export const SecondaryBtn: BtnComponent = styled(Btn)` background-color: ${Styles.C_MED_LIGHT_GRAY}; } - &:disabled { + &:disabled, + &.disabled { background-color: ${Styles.C_WHITE}; color: ${Styles.C_MED_GRAY}; } @@ -128,7 +133,8 @@ export const LightSecondaryBtn: BtnComponent = styled(SecondaryBtn)` background-color: ${Styles.OVERLAY_WHITE_20}; } - &:disabled { + &:disabled, + &.disabled { background-color: ${Styles.C_TRANSPARENT}; color: ${Styles.C_MED_GRAY}; } From 0eca6e419ee8ff90893b9b20b8c7350162ff7675 Mon Sep 17 00:00:00 2001 From: Mike Cousins Date: Tue, 28 Jul 2020 01:32:30 -0400 Subject: [PATCH 17/18] fixup: revert some unintentional test file changes --- api/tests/opentrons/data/mad_mag_v2.py | 2 +- api/tests/opentrons/data/testosaur_v2.py | 1 - app/src/calibration/api-types.js | 2 +- .../fetchCalibrationStatusEpic.test.js | 2 +- .../epic/fetchCalibrationStatusEpic.js | 1 + .../labware/__tests__/selectors.test.js | 4 +-- app/src/calibration/labware/selectors.js | 32 +++++++------------ app/src/components/FileInfo/index.js | 1 - 8 files changed, 17 insertions(+), 28 deletions(-) diff --git a/api/tests/opentrons/data/mad_mag_v2.py b/api/tests/opentrons/data/mad_mag_v2.py index 1159855774e..88e6d3caa87 100644 --- a/api/tests/opentrons/data/mad_mag_v2.py +++ b/api/tests/opentrons/data/mad_mag_v2.py @@ -14,7 +14,7 @@ def run(ctx): tr = ctx.load_labware('opentrons_96_tiprack_300ul', 1) mm = ctx.load_module('magnetic module', 4) lw = mm.load_labware('nest_96_wellplate_100ul_pcr_full_skirt') - right = ctx.load_instrument('p300_single', types.Mount.RIGHT, [tr]) + right = ctx.load_instrument('p300_single_gen2', types.Mount.RIGHT, [tr]) mm.disengage() diff --git a/api/tests/opentrons/data/testosaur_v2.py b/api/tests/opentrons/data/testosaur_v2.py index d50e7a5d255..dbe06d79b45 100644 --- a/api/tests/opentrons/data/testosaur_v2.py +++ b/api/tests/opentrons/data/testosaur_v2.py @@ -14,7 +14,6 @@ def run(ctx): tr = ctx.load_labware('opentrons_96_tiprack_300ul', 1) right = ctx.load_instrument('p300_single', types.Mount.RIGHT, [tr]) lw = ctx.load_labware('corning_96_wellplate_360ul_flat', 2) - ctx.pause() right.pick_up_tip() right.aspirate(10, lw.wells()[0].bottom()) right.dispense(10, lw.wells()[1].bottom()) diff --git a/app/src/calibration/api-types.js b/app/src/calibration/api-types.js index 3109a0c9e2f..37ae7bd4f61 100644 --- a/app/src/calibration/api-types.js +++ b/app/src/calibration/api-types.js @@ -36,7 +36,7 @@ export type CalibrationStatus = {| |} export type OffsetData = {| - value: Array, + value: [number, number, number], lastModified: string, |} diff --git a/app/src/calibration/epic/__tests__/fetchCalibrationStatusEpic.test.js b/app/src/calibration/epic/__tests__/fetchCalibrationStatusEpic.test.js index 418a3f2a36a..50ccf2272e7 100644 --- a/app/src/calibration/epic/__tests__/fetchCalibrationStatusEpic.test.js +++ b/app/src/calibration/epic/__tests__/fetchCalibrationStatusEpic.test.js @@ -2,7 +2,7 @@ import { setupEpicTestMocks, runEpicTest } from '../../../robot-api/__utils__' import * as Fixtures from '../../__fixtures__' import * as Actions from '../../actions' -import { calibrationEpic } from '../../epic' +import { calibrationEpic } from '..' const makeTriggerAction = robotName => Actions.fetchCalibrationStatus(robotName) diff --git a/app/src/calibration/epic/fetchCalibrationStatusEpic.js b/app/src/calibration/epic/fetchCalibrationStatusEpic.js index b117c99f6e9..49dd3027d0d 100644 --- a/app/src/calibration/epic/fetchCalibrationStatusEpic.js +++ b/app/src/calibration/epic/fetchCalibrationStatusEpic.js @@ -24,6 +24,7 @@ const mapResponseToAction: ResponseToActionMapper ) => { const { host, body, ...responseMeta } = response const meta = { ...originalAction.meta, response: responseMeta } + return response.ok ? Actions.fetchCalibrationStatusSuccess(host.name, body, meta) : Actions.fetchCalibrationStatusFailure(host.name, body, meta) diff --git a/app/src/calibration/labware/__tests__/selectors.test.js b/app/src/calibration/labware/__tests__/selectors.test.js index 3f6e1f67263..31f0f725e29 100644 --- a/app/src/calibration/labware/__tests__/selectors.test.js +++ b/app/src/calibration/labware/__tests__/selectors.test.js @@ -95,7 +95,7 @@ describe('labware calibration selectors', () => { expect(Selectors.getProtocolLabwareList(state, robotName)).toEqual([]) }) - it('maps RPC labware to loadName and displayName', () => { + it('maps RPC labware to object with displayName', () => { getModulesBySlot.mockReturnValue({}) expect(Selectors.getProtocolLabwareList(state, robotName)).toEqual([ @@ -250,7 +250,7 @@ describe('labware calibration selectors', () => { ]) }) - it('flattens out duplicate labware using quantity', () => { + it('flattens out duplicate labware using quantity field', () => { getLabware.mockReturnValue([ ({ type: wellPlate96Def.parameters.loadName, diff --git a/app/src/calibration/labware/selectors.js b/app/src/calibration/labware/selectors.js index 3326082f974..b9706e11261 100644 --- a/app/src/calibration/labware/selectors.js +++ b/app/src/calibration/labware/selectors.js @@ -68,31 +68,21 @@ export const getProtocolLabwareList: ( const { definition: def, loadName, namespace, version, parent } = lw const displayName = def ? getLabwareDisplayName(def) : loadName const parentDisplayName = parent ? getModuleDisplayName(parent) : null + const matchesLabwareIdentity = target => + target.loadName === loadName && + target.namespace === namespace && + target.version === version && + (!parent || target.parent === parent) - const quantity = baseLabwareList.filter(baseLw => { - return ( - baseLw.loadName === loadName && - baseLw.namespace === namespace && - baseLw.version === version - ) - }).length + const quantity = baseLabwareList.filter(matchesLabwareIdentity).length const calData = calibrations - .filter(({ attributes }) => { - return ( - attributes.loadName === loadName && - attributes.namespace === namespace && - attributes.version === version && - (!parent || attributes.parent === parent) - ) - }) + .filter(({ attributes }) => matchesLabwareIdentity(attributes)) .map(({ attributes }) => { - const calVector = attributes.calibrationData.offset.value - return { - x: round(calVector[0], 1), - y: round(calVector[1], 1), - z: round(calVector[2], 1), - } + const calVector = attributes.calibrationData.offset.value.map(n => + round(n, 1) + ) + return { x: calVector[0], y: calVector[1], z: calVector[2] } }) return { diff --git a/app/src/components/FileInfo/index.js b/app/src/components/FileInfo/index.js index a6763800085..d16005d151a 100644 --- a/app/src/components/FileInfo/index.js +++ b/app/src/components/FileInfo/index.js @@ -23,7 +23,6 @@ export type FileInfoProps = {| export function FileInfo(props: FileInfoProps): React.Node { const { robot, sessionLoaded, sessionHasSteps } = props - let uploadError = props.uploadError if (sessionLoaded && !uploadError && !sessionHasSteps) { From 674a8109b8bed5431ff387a5bfba0771d8739f21 Mon Sep 17 00:00:00 2001 From: Mike Cousins Date: Tue, 28 Jul 2020 12:34:16 -0400 Subject: [PATCH 18/18] fixup: address review comments --- app/src/calibration/labware/selectors.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/calibration/labware/selectors.js b/app/src/calibration/labware/selectors.js index b9706e11261..843b6eba6b5 100644 --- a/app/src/calibration/labware/selectors.js +++ b/app/src/calibration/labware/selectors.js @@ -35,7 +35,9 @@ type BaseProtocolLabware = {| // TODO(mc, 2020-07-27): this selector should move to a protocol-focused module // when we don't have to rely on RPC-state selectors for protocol equipment info -// NOTE(mc, 2020-07-27): due to how these endpo +// NOTE(mc, 2020-07-27): due to how these endpoints work, v1 labware will always +// come back as having "no calibration data". The `legacy` field is here so the +// UI can adjust its messaging accordingly export const getProtocolLabwareList: ( state: State, robotName: string @@ -72,7 +74,7 @@ export const getProtocolLabwareList: ( target.loadName === loadName && target.namespace === namespace && target.version === version && - (!parent || target.parent === parent) + (parent === null || target.parent === parent) const quantity = baseLabwareList.filter(matchesLabwareIdentity).length