Skip to content

Commit

Permalink
fix(app): use single source of truth for data in pipette settings sli…
Browse files Browse the repository at this point in the history
…deout (#13771)

closes RQA-1768
  • Loading branch information
shlokamin authored Oct 12, 2023
1 parent 20ca4a5 commit 326486a
Show file tree
Hide file tree
Showing 13 changed files with 188 additions and 126 deletions.
1 change: 1 addition & 0 deletions api-client/src/pipettes/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { getPipettes } from './getPipettes'
export { getPipetteSettings } from './getPipetteSettings'
export { updatePipetteSettings } from './updatePipetteSettings'

export * from './types'
export * from './__fixtures__'
18 changes: 15 additions & 3 deletions api-client/src/pipettes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ export interface FetchPipettesResponseBody {
right: FetchPipettesResponsePipette
}

interface PipetteSettingsField {
value: number | null | undefined
export interface PipetteSettingsField {
value: number | null | boolean | undefined
default: number
min?: number
max?: number
Expand All @@ -66,7 +66,7 @@ interface PipetteQuirksField {
interface QuirksField {
quirks?: PipetteQuirksField
}
type PipetteSettingsFieldsMap = QuirksField & {
export type PipetteSettingsFieldsMap = QuirksField & {
[fieldId: string]: PipetteSettingsField
}
export interface IndividualPipetteSettings {
Expand All @@ -77,3 +77,15 @@ export interface IndividualPipetteSettings {
type PipetteSettingsById = Partial<{ [id: string]: IndividualPipetteSettings }>

export type PipetteSettings = PipetteSettingsById

export interface PipetteSettingsUpdateFieldsMap {
[fieldId: string]: PipetteSettingsUpdateField
}

export type PipetteSettingsUpdateField = {
value: PipetteSettingsField['value']
} | null

export interface UpdatePipetteSettingsData {
fields: { [fieldId: string]: PipetteSettingsUpdateField }
}
21 changes: 21 additions & 0 deletions api-client/src/pipettes/updatePipetteSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { PATCH, request } from '../request'

import type { ResponsePromise } from '../request'
import type { HostConfig } from '../types'
import type {
IndividualPipetteSettings,
UpdatePipetteSettingsData,
} from './types'

export function updatePipetteSettings(
config: HostConfig,
pipetteId: string,
data: UpdatePipetteSettingsData
): ResponsePromise<IndividualPipetteSettings> {
return request<IndividualPipetteSettings, UpdatePipetteSettingsData>(
PATCH,
`/settings/pipettes/${pipetteId}`,
data,
config
)
}
22 changes: 11 additions & 11 deletions app/src/organisms/ConfigurePipette/ConfigForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@ import {
} from './ConfigFormGroup'

import type { FormikProps } from 'formik'
import type { FormValues } from './ConfigFormGroup'
import type {
PipetteSettingsField,
PipetteSettingsFieldsMap,
PipetteSettingsFieldsUpdate,
} from '../../redux/pipettes/types'

import type { FormValues } from './ConfigFormGroup'
UpdatePipetteSettingsData,
} from '@opentrons/api-client'

export interface DisplayFieldProps extends PipetteSettingsField {
name: string
Expand All @@ -38,7 +37,7 @@ export interface DisplayQuirkFieldProps {
export interface ConfigFormProps {
settings: PipetteSettingsFieldsMap
updateInProgress: boolean
updateSettings: (fields: PipetteSettingsFieldsUpdate) => unknown
updateSettings: (params: UpdatePipetteSettingsData) => void
groupLabels: string[]
formId: string
}
Expand Down Expand Up @@ -96,14 +95,16 @@ export class ConfigForm extends React.Component<ConfigFormProps> {
}

handleSubmit: (values: FormValues) => void = values => {
const params = mapValues<FormValues, number | boolean | null>(values, v => {
if (v === true || v === false) return v
const fields = mapValues<
FormValues,
{ value: PipetteSettingsField['value'] } | null
>(values, v => {
if (v === true || v === false) return { value: v }
if (v === '' || v == null) return null
return Number(v)
return { value: Number(v) }
})

// @ts-expect-error TODO updateSettings type doesn't include boolean for values of params, but they could be returned.
this.props.updateSettings(params)
this.props.updateSettings({ fields })
}

getFieldValue(
Expand Down Expand Up @@ -161,7 +162,6 @@ export class ConfigForm extends React.Component<ConfigFormProps> {
PipetteSettingsFieldsMap,
string | boolean
>(fields, f => {
// @ts-expect-error TODO: PipetteSettingsFieldsMap doesn't include a boolean value, despite checking for it here
if (f.value === true || f.value === false) return f.value
// @ts-expect-error(sa, 2021-05-27): avoiding src code change, use optional chain to access f.value
return f.value !== f.default ? f.value.toString() : ''
Expand Down
2 changes: 1 addition & 1 deletion app/src/organisms/ConfigurePipette/ConfigFormGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export function ConfigInput(props: ConfigInputProps): JSX.Element {
const { field } = props
const { name, units, displayName } = field
const id = makeId(field.name)
const _default = field.default.toString()
const _default = field.default?.toString()
return (
<ConfigFormRow label={displayName} labelFor={id}>
<Field name={name}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ describe('ConfigurePipette', () => {

beforeEach(() => {
props = {
isUpdateLoading: false,
updateError: null,
settings: mockPipetteSettingsFieldsMap,
robotName: mockRobotName,
updateRequest: { status: 'pending' },
updateSettings: jest.fn(),
closeModal: jest.fn(),
formId: 'id',
Expand Down
39 changes: 16 additions & 23 deletions app/src/organisms/ConfigurePipette/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,31 @@ import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { Box } from '@opentrons/components'

import { SUCCESS, FAILURE, PENDING } from '../../redux/robot-api'
import { ConfigForm } from './ConfigForm'
import { ConfigErrorBanner } from './ConfigErrorBanner'

import type {
PipetteSettingsFieldsUpdate,
PipetteSettingsFieldsMap,
} from '../../redux/pipettes/types'
import type { RequestState } from '../../redux/robot-api/types'
UpdatePipetteSettingsData,
} from '@opentrons/api-client'

interface Props {
closeModal: () => void
updateRequest: RequestState | null
updateSettings: (fields: PipetteSettingsFieldsUpdate) => void
updateSettings: (params: UpdatePipetteSettingsData) => void
updateError: Error | null
isUpdateLoading: boolean
robotName: string
formId: string
settings: PipetteSettingsFieldsMap
}

export function ConfigurePipette(props: Props): JSX.Element {
const { closeModal, updateRequest, updateSettings, formId, settings } = props
const {
updateSettings,
updateError,
isUpdateLoading,
formId,
settings,
} = props
const { t } = useTranslation('device_details')

const groupLabels = [
Expand All @@ -31,25 +35,14 @@ export function ConfigurePipette(props: Props): JSX.Element {
t('power_force'),
]

const updateError: string | null =
updateRequest && updateRequest.status === FAILURE
? // @ts-expect-error(sa, 2021-05-27): avoiding src code change, need to type narrow
updateRequest.error.message || t('an_error_occurred_while_updating')
: null

// when an in-progress request completes, close modal if response was ok
React.useEffect(() => {
if (updateRequest?.status === SUCCESS) {
closeModal()
}
}, [updateRequest, closeModal])

return (
<Box zIndex={1}>
{updateError && <ConfigErrorBanner message={updateError} />}
{updateError != null && (
<ConfigErrorBanner message={updateError.message} />
)}
<ConfigForm
settings={settings}
updateInProgress={updateRequest?.status === PENDING}
updateInProgress={isUpdateLoading}
updateSettings={updateSettings}
groupLabels={groupLabels}
formId={formId}
Expand Down
6 changes: 2 additions & 4 deletions app/src/organisms/Devices/PipetteCard/PipetteOverflowMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@ import {
import { MenuItem } from '../../../atoms/MenuList/MenuItem'
import { Divider } from '../../../atoms/structure'

import type {
Mount,
PipetteSettingsFieldsMap,
} from '../../../redux/pipettes/types'
import type { Mount } from '../../../redux/pipettes/types'
import type { PipetteSettingsFieldsMap } from '@opentrons/api-client'

interface PipetteOverflowMenuProps {
pipetteSpecs: PipetteModelSpecs | null
Expand Down
44 changes: 14 additions & 30 deletions app/src/organisms/Devices/PipetteCard/PipetteSettingsSlideout.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import last from 'lodash/last'
import { useSelector } from 'react-redux'
import { Flex } from '@opentrons/components'
import { PipetteModelSpecs } from '@opentrons/shared-data'
import { updatePipetteSettings } from '../../../redux/pipettes'
import { useUpdatePipetteSettingsMutation } from '@opentrons/react-api-client'
import { Slideout } from '../../../atoms/Slideout'
import {
getRequestById,
PENDING,
useDispatchApiRequest,
} from '../../../redux/robot-api'
import { ConfigFormSubmitButton } from '../../ConfigurePipette/ConfigFormSubmitButton'
import { ConfigurePipette } from '../../ConfigurePipette'

import type {
AttachedPipette,
PipetteSettingsFieldsUpdate,
PipetteSettingsFieldsMap,
} from '../../../redux/pipettes/types'
import type { State } from '../../../redux/types'
import type { AttachedPipette } from '../../../redux/pipettes/types'
import type { PipetteSettingsFieldsMap } from '@opentrons/api-client'

interface PipetteSettingsSlideoutProps {
robotName: string
pipetteName: PipetteModelSpecs['displayName']
Expand All @@ -41,33 +31,27 @@ export const PipetteSettingsSlideout = (
settings,
} = props
const { t } = useTranslation('device_details')
const [dispatchRequest, requestIds] = useDispatchApiRequest()
const updateSettings = (fields: PipetteSettingsFieldsUpdate): void => {
dispatchRequest(updatePipetteSettings(robotName, pipetteId, fields))
}
const latestRequestId = last(requestIds)
const updateRequest = useSelector((state: State) =>
latestRequestId != null ? getRequestById(state, latestRequestId) : null
)
const {
updatePipetteSettings,
isLoading,
error,
} = useUpdatePipetteSettingsMutation(pipetteId, { onSuccess: onCloseClick })

const FORM_ID = `configurePipetteForm_${pipetteId}`

return (
<Slideout
title={t('pipette_settings', { pipetteName: pipetteName })}
onCloseClick={onCloseClick}
isExpanded={isExpanded}
footer={
<ConfigFormSubmitButton
disabled={updateRequest?.status === PENDING}
formId={FORM_ID}
/>
}
footer={<ConfigFormSubmitButton disabled={isLoading} formId={FORM_ID} />}
>
<Flex data-testid={`PipetteSettingsSlideout_${robotName}_${pipetteId}`}>
<ConfigurePipette
closeModal={onCloseClick}
updateRequest={updateRequest}
updateSettings={updateSettings}
updateSettings={updatePipetteSettings}
updateError={error}
isUpdateLoading={isLoading}
robotName={robotName}
formId={FORM_ID}
settings={settings}
Expand Down
Loading

0 comments on commit 326486a

Please sign in to comment.