Skip to content

Commit

Permalink
refactor(protocol-designer): migrate from flow to TS (#7883)
Browse files Browse the repository at this point in the history
* Convert flow to TS

Co-authored-by: Curt Elsasser <[email protected]>
Co-authored-by: Sarah Breen <[email protected]>
  • Loading branch information
3 people committed Jul 7, 2021
1 parent 089b6a6 commit 748983d
Show file tree
Hide file tree
Showing 520 changed files with 14,544 additions and 17,487 deletions.
8 changes: 7 additions & 1 deletion .madgerc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
"detectiveOptions": {
"es6": {
"skipTypeImports": true
}
},
"ts": {
"skipTypeImports": true
},
"tsx": {
"skipTypeImports": true
}
}
}
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ clean-ts:
# TODO: Ian 2019-12-17 gradually add components and shared-data
.PHONY: circular-dependencies-js
circular-dependencies-js:
madge $(and $(CI),--no-spinner --no-color) --circular protocol-designer/src/index.js
madge $(and $(CI),--no-spinner --no-color) --circular protocol-designer/src/index.tsx
madge $(and $(CI),--no-spinner --no-color) --circular step-generation/src/index.ts
madge $(and $(CI),--no-spinner --no-color) --circular labware-library/src/index.tsx
madge $(and $(CI),--no-spinner --no-color) --circular app/src/index.tsx
Expand Down
2 changes: 1 addition & 1 deletion app-shell/src/system-info/network-interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function getActiveInterfaces(): NetworkInterface[] {
const ifaces = os.networkInterfaces()

return Object.keys(ifaces).flatMap<NetworkInterface>((name: string) => {
// $FlowFixMe(mc, 2020-05-27): Flow def of os.networkInterfaces return is incomplete
// @ts-expect-error(sa, 2021-6-28): this could be undefined because Object.keys returns a list of strings (too generic)
return ifaces[name]
.filter(iface => !iface.internal)
.map(iface => ({ ...iface, name }))
Expand Down
4 changes: 2 additions & 2 deletions app/src/pages/Robots/InstrumentSettings/PipetteInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ export function PipetteInfo(props: PipetteInfoProps): JSX.Element {
<PrimaryBtn
{...(disabledReason ? changePipTargetProps : {})}
as={disabledReason ? 'button' : Link}
// @ts-expect-error TODO: 'to' prop can only be passed if as={Link}
to={changeUrl}
// @ts-expect-error TODO: cast disabledReason to bool
disabled={disabledReason}
title="changePipetteButton"
width={SIZE_4}
Expand All @@ -128,8 +128,8 @@ export function PipetteInfo(props: PipetteInfoProps): JSX.Element {
<SecondaryBtn
{...(disabledReason ? settingsTargetProps : {})}
as={disabledReason ? 'button' : Link}
// @ts-expect-error TODO: 'to' prop can only be passed if as={Link}
to={settingsUrl}
// @ts-expect-error TODO: cast disabledReason to bool
disabled={disabledReason}
title="pipetteSettingsButton"
width={SIZE_4}
Expand Down
2 changes: 2 additions & 0 deletions app/src/pages/Robots/InstrumentSettings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export function InstrumentSettings(
render={routeProps => (
<ChangePipette
robotName={robotName}
// @ts-expect-error not a valid Mount type
mount={routeProps.match.params.mount}
closeModal={routeProps.history.goBack}
/>
Expand All @@ -55,6 +56,7 @@ export function InstrumentSettings(
render={routeProps => (
<ConfigurePipette
robotName={robotName}
// @ts-expect-error not a valid Mount type
mount={routeProps.match.params.mount}
closeModal={routeProps.history.goBack}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ export function AdvancedSettingsCard(
<Divider />
<Flex justifyContent={JUSTIFY_SPACE_BETWEEN} padding={SPACING_3}>
<LabeledValue label={t('reset_label')} value={t('reset_description')} />
{/* @ts-expect-error TODO: Link does not have a disable prop, is this prop achieving anything useful */}
<SecondaryBtn
disabled={controlsDisabled}
as={Link}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ describe('CalibrationCard', () => {

const realBlob = global.Blob
beforeAll(() => {
// @ts-expect-error(sa, 2021-6-28): not a valid blob interface
global.Blob = function (content: any, options: any) {
return { content, options }
}
Expand Down
1 change: 1 addition & 0 deletions app/src/redux/protocol/__tests__/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ describe('protocol actions', () => {
mockReader = { readAsText: jest.fn(), readAsArrayBuffer: jest.fn() }

store = mockStore({})
// @ts-expect-error(sa, 2021-6-28): not a valid FileReader interface
global.FileReader = jest.fn(() => mockReader)
getFeatureFlags.mockReturnValue({
enableBundleUpload: false,
Expand Down
2 changes: 2 additions & 0 deletions app/src/redux/robot-api/__tests__/http.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe('robot-api http client', () => {
let robot: RobotHost

beforeAll(() => {
// @ts-expect-error(sa, 2021-6-28): global.fetch and node fetch have different interfaces
global.fetch = fetch
testApp = express()
testApp.use((express as any).json())
Expand Down Expand Up @@ -53,6 +54,7 @@ describe('robot-api http client', () => {
})

afterAll(() => {
// @ts-expect-error(sa, 2021-6-28): can't delete non optional properties
delete global.fetch

if (testServer) {
Expand Down
2 changes: 2 additions & 0 deletions components/src/forms/SelectField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ export function SelectField(props: SelectFieldProps): JSX.Element {
const allOptions = options.flatMap(og => og.options || [og])
const value = find(allOptions, opt => opt.value === props.value) || null
const caption = error || props.caption
// @ts-expect-error(sa, 2021-6-23): cast value to boolean
const captionCx = cx(styles.select_caption, { [styles.error]: error })
// @ts-expect-error(sa, 2021-6-23): cast value to boolean
const fieldCx = cx(styles.select_field, { [styles.error]: error }, className)

return (
Expand Down
2 changes: 1 addition & 1 deletion components/src/lists/ListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const ListItem = React.forwardRef(
(props: ListItemProps, ref: React.ForwardedRef<HTMLLIElement>) => {
const { url, isDisabled, iconName, activeClassName, exact } = props
const onClick = props.onClick && !isDisabled ? props.onClick : undefined

// @ts-expect-error(sa, 2021-6-23): cast value to boolean
const className = classnames(props.className, styles.list_item, {
[styles.disabled]: isDisabled,
[styles.clickable]: onClick,
Expand Down
2 changes: 1 addition & 1 deletion components/src/lists/TitledList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export function TitledList(props: TitledListProps): JSX.Element {
[styles.titled_list_selected]: !disabled && props.selected,
[styles.hover_border]: !disabled && props.hovered,
})

// @ts-expect-error(sa, 2021-6-23): cast value to boolean
const titleBarClass = cx(styles.title_bar, {
[styles.clickable]: props.onClick,
})
Expand Down
2 changes: 1 addition & 1 deletion components/src/modals/Overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export interface OverlayProps {
*/
export function Overlay(props: OverlayProps): JSX.Element {
const { alertOverlay, onClick } = props

// @ts-expect-error(sa, 2021-6-23): cast value to boolean
const className = cx(styles.overlay, {
[styles.clickable]: onClick,
[styles.alert_modal_overlay]: alertOverlay,
Expand Down
1 change: 1 addition & 0 deletions components/src/structure/TitleBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export function TitleBar(props: TitleBarProps): JSX.Element {
{...back}
/>
)}
{/* @ts-expect-error(sa, 2021-6-23): cast value to boolean */}
<h1 className={cx(styles.title, { [styles.right]: back })}>{title}</h1>
{separator}
{subheading}
Expand Down
1 change: 1 addition & 0 deletions discovery-client/src/mdns-browser/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export function getSystemInterfaces(): NetworkInterface[] {
const interfaceMap = networkInterfaces()

return Object.keys(interfaceMap).flatMap(ifaceName => {
// @ts-expect-error TS thinks interfaceMap[ifaceName] could be undefined because Object.keys returns a list of strings (too generic)
return interfaceMap[ifaceName]
.filter(iface => iface.family === 'IPv4' && !iface.internal)
.map(iface => ({ name: ifaceName, address: iface.address }))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import fieldStyles from './fieldStyles.css'
interface Props {
name: keyof LabwareFields
options: RadioGroupProps['options']
labelTextClassName?: string | null | undefined
labelTextClassName?: string | null
}

export const RadioField = (props: Props): JSX.Element => (
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@
"react-docgen-typescript": "^1.21.0",
"react-dom": "16.8.6",
"react-snap": "^1.23.0",
"react-test-renderer": "^16.8.6",
"react-test-renderer": "16.8.6",
"redux-mock-store": "^1.5.3",
"rehype": "^9.0.0",
"rehype-urls": "^1.0.0",
Expand Down
2 changes: 2 additions & 0 deletions protocol-designer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# type caches
lib/
1 change: 0 additions & 1 deletion protocol-designer/benchmarks/timelineGeneration.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @flow
import assert from 'assert'
import bench from 'nanobench'
import {
Expand Down
2 changes: 1 addition & 1 deletion protocol-designer/docs/STEP_FORMS_AND_PROTOCOL_TIMELINE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Motivation: ultimately, `step-generation`'s main job is to build a timeline that

In practice, these functions are never written directly, `CommandCreator` functions are always created by other functions. For a simple example, see `aspirate.js`: `const aspirate = (args: AspirateParams): CommandCreator => (...) => {...}`. We also use the phrase "command creator" to refer to a function that returns a `CommandCreator`. (This wording needs to be refactored because it's very confusing. Sorry!)

Additionally, some of these functions return `Array<CommandCreator>`; we call these "compound command creators". Compound command creators can be curried into non-compound command creators by use of `reduceCommandCreators` util. We plan to homogenize these to make it all simpler. Part of the complexity comes from the requirement that we want to control the granularity of commands and frames: a single "frame" can encompass one or more atomic commands. For example a Transfer Step in PD should be a single frame in the PD timeline from `getRobotStateTimeline`, but it may contain many pickUpTip/aspirate/dispense/touchTip/dropTip/etc atomic commands. The meaning of that granularity depends on how the consumer (eg PD) wants to use it. It's not useful to have hundreds of frames representing every intermediate robot state throughout a protocol, we only want to remember the intermediate states associated with key points in time (eg, a Step).
Additionally, some of these functions return `CommandCreator[]`; we call these "compound command creators". Compound command creators can be curried into non-compound command creators by use of `reduceCommandCreators` util. We plan to homogenize these to make it all simpler. Part of the complexity comes from the requirement that we want to control the granularity of commands and frames: a single "frame" can encompass one or more atomic commands. For example a Transfer Step in PD should be a single frame in the PD timeline from `getRobotStateTimeline`, but it may contain many pickUpTip/aspirate/dispense/touchTip/dropTip/etc atomic commands. The meaning of that granularity depends on how the consumer (eg PD) wants to use it. It's not useful to have hundreds of frames representing every intermediate robot state throughout a protocol, we only want to remember the intermediate states associated with key points in time (eg, a Step).

### Creating a timeline

Expand Down
5 changes: 4 additions & 1 deletion protocol-designer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
"@opentrons/components": "4.4.0",
"@opentrons/step-generation": "4.4.0",
"@typeform/embed": "0.16.0",
"@types/ua-parser-js": "0.7.36",
"@types/redux-actions": "2.6.1",
"@types/uuid": "8.3.0",
"ajv": "6.10.2",
"classnames": "2.2.5",
"cookie": "0.3.1",
Expand All @@ -42,7 +45,7 @@
"react-redux": "7.2.1",
"redux": "4.0.5",
"redux-actions": "2.2.1",
"redux-thunk": "2.2.0",
"redux-thunk": "2.3.0",
"reselect": "4.0.0",
"ua-parser-js": "^0.7.23",
"uuid": "3.3.2",
Expand Down
6 changes: 2 additions & 4 deletions protocol-designer/src/__tests__/persist.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
// @flow

import * as persist from '../persist'

describe('persist', () => {
let getItemSpy
let setItemSpy
let getItemSpy: jest.SpyInstance<any, unknown[]>
let setItemSpy: jest.SpyInstance<any, unknown[]>

beforeEach(() => {
const LocalStorageProto = Object.getPrototypeOf(global.localStorage)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @flow
import Ajv from 'ajv'
import glob from 'glob'
import last from 'lodash/last'
Expand All @@ -10,7 +9,7 @@ import protocolV5Schema from '@opentrons/shared-data/protocol/schemas/5.json'
import labwareV2Schema from '@opentrons/shared-data/labware/schemas/2.json'

// TODO: copied from createFile.test.js
const getAjvValidator = _protocolSchema => {
const getAjvValidator = (_protocolSchema: object) => {
const ajv = new Ajv({
allErrors: true,
jsonPointers: true,
Expand All @@ -21,7 +20,11 @@ const getAjvValidator = _protocolSchema => {
return validateProtocol
}

const expectResultToMatchSchema = (testName, result, _protocolSchema): void => {
const expectResultToMatchSchema = (
testName: string,
result: any,
_protocolSchema: object
): void => {
const validate = getAjvValidator(_protocolSchema)
const valid = validate(result)
const validationErrors = validate.errors
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @flow
import { flattenNestedProperties } from '../utils/flattenNestedProperties'

describe('flattenNestedProperties', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @flow
import { when, resetAllWhenMocks } from 'jest-when'
import { reduxActionToAnalyticsEvent } from '../middleware'
import { getFileMetadata } from '../../file-data/selectors'
Expand All @@ -7,19 +6,23 @@ import {
getPipetteEntities,
getSavedStepForms,
} from '../../step-forms/selectors'
import type { FileMetadataFields } from '../../file-data/types'
import type { SaveStepFormsMultiAction } from '../../step-forms/actions'
import { SaveStepFormsMultiAction } from '../../step-forms/actions'

jest.mock('../../file-data/selectors')
jest.mock('../../step-forms/selectors')

const getFileMetadataMock: JestMockFn<any, FileMetadataFields> = getFileMetadata
const getArgsAndErrorsByStepIdMock: JestMockFn<
any,
any
> = getArgsAndErrorsByStepId
const getPipetteEntitiesMock: JestMockFn<any, any> = getPipetteEntities
const getSavedStepFormsMock: JestMockFn<any, any> = getSavedStepForms
const getFileMetadataMock = getFileMetadata as jest.MockedFunction<
typeof getFileMetadata
>
const getArgsAndErrorsByStepIdMock = getArgsAndErrorsByStepId as jest.MockedFunction<
typeof getArgsAndErrorsByStepId
>
const getPipetteEntitiesMock = getPipetteEntities as jest.MockedFunction<
typeof getPipetteEntities
>
const getSavedStepFormsMock = getSavedStepForms as jest.MockedFunction<
typeof getSavedStepForms
>
let fooState: any
beforeEach(() => {
fooState = {}
Expand Down Expand Up @@ -52,6 +55,7 @@ describe('reduxActionToAnalyticsEvent', () => {
getArgsAndErrorsByStepIdMock.mockReturnValue({
stepId: {
stepArgs: {
// @ts-expect-error id is not on type CommandCreatorArgs
id: 'stepId',
pipette: 'pipetteId',
otherField: 123,
Expand All @@ -60,6 +64,7 @@ describe('reduxActionToAnalyticsEvent', () => {
},
})
getPipetteEntitiesMock.mockReturnValue({
// @ts-expect-error 'some_pipette_spec_name' isn't a valid pipette type
pipetteId: { name: 'some_pipette_spec_name' },
})

Expand Down Expand Up @@ -110,7 +115,9 @@ describe('reduxActionToAnalyticsEvent', () => {
when(getSavedStepFormsMock)
.calledWith(expect.anything())
.mockReturnValue({
// @ts-expect-error missing fields from test object
id_1: { stepType: 'moveLiquid' },
// @ts-expect-error missing fields from test object
id_2: { stepType: 'moveLiquid' },
})

Expand Down Expand Up @@ -138,7 +145,9 @@ describe('reduxActionToAnalyticsEvent', () => {
when(getSavedStepFormsMock)
.calledWith(expect.anything())
.mockReturnValue({
// @ts-expect-error missing fields from test object
id_1: { stepType: 'mix' },
// @ts-expect-error missing fields from test object
id_2: { stepType: 'mix' },
})

Expand Down Expand Up @@ -166,7 +175,9 @@ describe('reduxActionToAnalyticsEvent', () => {
when(getSavedStepFormsMock)
.calledWith(expect.anything())
.mockReturnValue({
// @ts-expect-error missing fields from test object
id_1: { stepType: 'mix' },
// @ts-expect-error missing fields from test object
id_2: { stepType: 'moveLiquid' },
})

Expand Down
31 changes: 15 additions & 16 deletions protocol-designer/src/analytics/actions.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
// @flow
import { initializeFullstory, shutdownFullstory } from './fullstory'
import { setMixpanelTracking } from './mixpanel'
import type { AnalyticsEvent } from './mixpanel'
import { setMixpanelTracking, AnalyticsEvent } from './mixpanel'

export type SetOptIn = {|
type: 'SET_OPT_IN',
payload: boolean,
|}
export interface SetOptIn {
type: 'SET_OPT_IN'
payload: boolean
}

const _setOptIn = (payload: $PropertyType<SetOptIn, 'payload'>): SetOptIn => {
const _setOptIn = (payload: SetOptIn['payload']): SetOptIn => {
// side effects
if (payload) {
initializeFullstory()
Expand All @@ -26,13 +24,11 @@ const _setOptIn = (payload: $PropertyType<SetOptIn, 'payload'>): SetOptIn => {

export const optIn = (): SetOptIn => _setOptIn(true)
export const optOut = (): SetOptIn => _setOptIn(false)

export type AnalyticsEventAction = {|
type: 'ANALYTICS_EVENT',
payload: AnalyticsEvent,
meta?: mixed,
|}

export interface AnalyticsEventAction {
type: 'ANALYTICS_EVENT'
payload: AnalyticsEvent
meta?: unknown
}
// NOTE: this action creator should only be used for special cases where you want to
// report an analytics event but you do not have any Redux action that sensibly represents
// that analytics event.
Expand All @@ -45,4 +41,7 @@ export type AnalyticsEventAction = {|
// we need to read opt-in status from the Redux state.
export const analyticsEvent = (
payload: AnalyticsEvent
): AnalyticsEventAction => ({ type: 'ANALYTICS_EVENT', payload })
): AnalyticsEventAction => ({
type: 'ANALYTICS_EVENT',
payload,
})
Loading

0 comments on commit 748983d

Please sign in to comment.