Skip to content

Commit

Permalink
feat(app, api-client, react-api-client): add api-client method for pr…
Browse files Browse the repository at this point in the history
…otocol reanalysis (#14878)

closes AUTH-118
  • Loading branch information
ncdiehl11 authored Apr 12, 2024
1 parent b0fb14f commit 485bf0e
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 24 deletions.
28 changes: 28 additions & 0 deletions api-client/src/protocols/createProtocolAnalysis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { POST, request } from '../request'

import type { ProtocolAnalysisSummary } from '@opentrons/shared-data'
import type { ResponsePromise } from '../request'
import type { HostConfig } from '../types'
import type { RunTimeParameterCreateData } from '../runs'

interface CreateProtocolAnalysisData {
runTimeParameterValues: RunTimeParameterCreateData
forceReAnalyze: boolean
}

export function createProtocolAnalysis(
config: HostConfig,
protocolKey: string,
runTimeParameterValues?: RunTimeParameterCreateData,
forceReAnalyze?: boolean
): ResponsePromise<ProtocolAnalysisSummary[]> {
const data = {
runTimeParameterValues: runTimeParameterValues ?? {},
forceReAnalyze: forceReAnalyze ?? false,
}
const response = request<
ProtocolAnalysisSummary[],
{ data: CreateProtocolAnalysisData }
>(POST, `/protocols/${protocolKey}/analyses`, { data }, config)
return response
}
1 change: 1 addition & 0 deletions api-client/src/protocols/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export { getProtocolAnalyses } from './getProtocolAnalyses'
export { getProtocolAnalysisAsDocument } from './getProtocolAnalysisAsDocument'
export { deleteProtocol } from './deleteProtocol'
export { createProtocol } from './createProtocol'
export { createProtocolAnalysis } from './createProtocolAnalysis'
export { getProtocols } from './getProtocols'
export { getProtocolIds } from './getProtocolIds'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import * as React from 'react'
import { when } from 'vitest-when'
import { it, describe, beforeEach, vi, expect } from 'vitest'
import { fireEvent, screen } from '@testing-library/react'
import { useCreateRunMutation, useHost } from '@opentrons/react-api-client'
import {
useCreateProtocolAnalysisMutation,
useCreateRunMutation,
useHost,
} from '@opentrons/react-api-client'
import { i18n } from '../../../i18n'
import { renderWithProviders } from '../../../__testing-utils__'
import { ProtocolSetupParameters } from '..'
Expand All @@ -24,6 +28,7 @@ vi.mock('react-router-dom', async importOriginal => {
}
})
const MOCK_HOST_CONFIG: HostConfig = { hostname: 'MOCK_HOST' }
const mockCreateProtocolAnalysis = vi.fn()
const mockCreateRun = vi.fn()
const render = (
props: React.ComponentProps<typeof ProtocolSetupParameters>
Expand All @@ -43,6 +48,9 @@ describe('ProtocolSetupParameters', () => {
}
vi.mocked(ChooseEnum).mockReturnValue(<div>mock ChooseEnum</div>)
vi.mocked(useHost).mockReturnValue(MOCK_HOST_CONFIG)
when(vi.mocked(useCreateProtocolAnalysisMutation))
.calledWith(expect.anything(), expect.anything())
.thenReturn({ createProtocolAnalysis: mockCreateProtocolAnalysis } as any)
when(vi.mocked(useCreateRunMutation))
.calledWith(expect.anything())
.thenReturn({ createRun: mockCreateRun } as any)
Expand All @@ -62,10 +70,9 @@ describe('ProtocolSetupParameters', () => {
})
it('renders the other setting when boolean param is selected', () => {
render(props)
screen.getByText('Off')
expect(screen.getAllByText('On')).toHaveLength(3)
expect(screen.getAllByText('On')).toHaveLength(2)
fireEvent.click(screen.getByText('Dry Run'))
expect(screen.getAllByText('On')).toHaveLength(4)
expect(screen.getAllByText('On')).toHaveLength(3)
})
it('renders the back icon and calls useHistory', () => {
render(props)
Expand All @@ -88,6 +95,5 @@ describe('ProtocolSetupParameters', () => {
const title = screen.getByText('Reset parameter values?')
fireEvent.click(screen.getByRole('button', { name: 'Go back' }))
expect(title).not.toBeInTheDocument()
// TODO(jr, 3/19/24): wire up the confirm button
})
})
25 changes: 23 additions & 2 deletions app/src/organisms/ProtocolSetupParameters/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { useHistory } from 'react-router-dom'
import { useCreateRunMutation, useHost } from '@opentrons/react-api-client'
import {
useCreateProtocolAnalysisMutation,
useCreateRunMutation,
useHost,
} from '@opentrons/react-api-client'
import { useQueryClient } from 'react-query'
import {
ALIGN_CENTER,
Expand Down Expand Up @@ -51,7 +55,12 @@ export function ProtocolSetupParameters({
const [
runTimeParametersOverrides,
setRunTimeParametersOverrides,
] = React.useState<RunTimeParameter[]>(runTimeParameters)
] = React.useState<RunTimeParameter[]>(
// present defaults rather than last-set value
runTimeParameters.map(param => {
return { ...param, value: param.default }
})
)

const updateParameters = (
value: boolean | string | number,
Expand Down Expand Up @@ -85,6 +94,14 @@ export function ProtocolSetupParameters({
}
}

const runTimeParameterValues = getRunTimeParameterValuesForRun(
runTimeParametersOverrides
)
const { createProtocolAnalysis } = useCreateProtocolAnalysisMutation(
protocolId,
host
)

const { createRun, isLoading } = useCreateRunMutation({
onSuccess: data => {
queryClient
Expand All @@ -96,6 +113,10 @@ export function ProtocolSetupParameters({
})
const handleConfirmValues = (): void => {
setStartSetup(true)
createProtocolAnalysis({
protocolKey: protocolId,
runTimeParameterValues: runTimeParameterValues,
})
createRun({
protocolId,
labwareOffsets,
Expand Down
5 changes: 2 additions & 3 deletions app/src/pages/ProtocolDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -346,13 +346,12 @@ export function ProtocolDetails(): JSX.Element | null {
let pinnedProtocolIds = useSelector(getPinnedProtocolIds) ?? []
const pinned = pinnedProtocolIds.includes(protocolId)

const { data: protocolData } = useProtocolQuery(protocolId)
const {
data: mostRecentAnalysis,
} = useProtocolAnalysisAsDocumentQuery(
protocolId,
last(protocolData?.data.analysisSummaries)?.id ?? null,
{ enabled: protocolData != null }
last(protocolRecord?.data.analysisSummaries)?.id ?? null,
{ enabled: protocolRecord != null }
)

const shouldApplyOffsets = useSelector(getApplyHistoricOffsets)
Expand Down
26 changes: 12 additions & 14 deletions app/src/pages/ProtocolSetup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ import {
getProtocolUsesGripper,
} from '../../organisms/ProtocolSetupInstruments/utils'
import {
useProtocolHasRunTimeParameters,
useRunControls,
useRunStatus,
} from '../../organisms/RunTimeControl/hooks'
Expand Down Expand Up @@ -257,9 +256,6 @@ function PrepareToRun({
const { t, i18n } = useTranslation(['protocol_setup', 'shared'])
const history = useHistory()
const { makeSnackbar } = useToaster()
const hasRunTimeParameters = useProtocolHasRunTimeParameters(runId)
console.log(hasRunTimeParameters)
// Watch for scrolling to toggle dropshadow
const scrollRef = React.useRef<HTMLDivElement>(null)
const [isScrolled, setIsScrolled] = React.useState<boolean>(false)
const observer = new IntersectionObserver(([entry]) => {
Expand Down Expand Up @@ -366,6 +362,12 @@ function PrepareToRun({
})
const moduleCalibrationStatus = useModuleCalibrationStatus(robotName, runId)

const runTimeParameters = mostRecentAnalysis?.runTimeParameters ?? []
const hasRunTimeParameters = runTimeParameters.length > 0
const hasCustomRunTimeParameters = runTimeParameters.some(
parameter => parameter.value !== parameter.default
)

const [
showConfirmCancelModal,
setShowConfirmCancelModal,
Expand Down Expand Up @@ -623,11 +625,11 @@ function PrepareToRun({
doorStatus?.data.status === 'open' &&
doorStatus?.data.doorRequiredClosedForProtocol

// TODO(Jr, 3/20/24): wire up custom values
const hasCustomValues = false
const parametersDetail = hasCustomValues
? t('custom_values')
: t('default_values')
const parametersDetail = hasRunTimeParameters
? hasCustomRunTimeParameters
? t('custom_values')
: t('default_values')
: t('no_parameters_specified')

return (
<>
Expand Down Expand Up @@ -733,11 +735,7 @@ function PrepareToRun({
<ProtocolSetupStep
onClickSetupStep={() => setSetupScreen('view only parameters')}
title={t('parameters')}
detail={t(
hasRunTimeParameters
? parametersDetail
: t('no_parameters_specified')
)}
detail={parametersDetail}
subDetail={null}
status="general"
disabled={!hasRunTimeParameters}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import * as React from 'react'
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { QueryClient, QueryClientProvider } from 'react-query'
import { act, renderHook, waitFor } from '@testing-library/react'
import { createProtocolAnalysis } from '@opentrons/api-client'
import { useHost } from '../../api'
import { useCreateProtocolAnalysisMutation } from '..'
import type { HostConfig, Response } from '@opentrons/api-client'
import type { ProtocolAnalysisSummary } from '@opentrons/shared-data'

vi.mock('@opentrons/api-client')
vi.mock('../../api/useHost')

const HOST_CONFIG: HostConfig = { hostname: 'localhost' }
const ANALYSIS_SUMMARY_RESPONSE = [
{ id: 'fakeAnalysis1', status: 'completed' },
{ id: 'fakeAnalysis2', status: 'pending' },
] as ProtocolAnalysisSummary[]

describe('useCreateProtocolAnalysisMutation hook', () => {
let wrapper: React.FunctionComponent<{ children: React.ReactNode }>

beforeEach(() => {
const queryClient = new QueryClient()
const clientProvider: React.FunctionComponent<{
children: React.ReactNode
}> = ({ children }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
)
wrapper = clientProvider
})

it('should return no data when calling createProtocolAnalysis if the request fails', async () => {
vi.mocked(useHost).mockReturnValue(HOST_CONFIG)
vi.mocked(createProtocolAnalysis).mockRejectedValue('oh no')

const { result } = renderHook(
() => useCreateProtocolAnalysisMutation('fake-protocol-key'),
{
wrapper,
}
)

expect(result.current.data).toBeUndefined()
result.current.createProtocolAnalysis({
protocolKey: 'fake-protocol-key',
runTimeParameterValues: {},
})
await waitFor(() => {
expect(result.current.data).toBeUndefined()
})
})

it('should create an array of ProtocolAnalysisSummaries when calling the createProtocolAnalysis callback', async () => {
vi.mocked(useHost).mockReturnValue(HOST_CONFIG)
vi.mocked(createProtocolAnalysis).mockResolvedValue({
data: ANALYSIS_SUMMARY_RESPONSE,
} as Response<ProtocolAnalysisSummary[]>)

const { result } = renderHook(
() => useCreateProtocolAnalysisMutation('fake-protocol-key'),
{
wrapper,
}
)
act(() =>
result.current.createProtocolAnalysis({
protocolKey: 'fake-protocol-key',
runTimeParameterValues: {},
})
)

await waitFor(() => {
expect(result.current.data).toEqual(ANALYSIS_SUMMARY_RESPONSE)
})
})
})
1 change: 1 addition & 0 deletions react-api-client/src/protocols/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export { useProtocolQuery } from './useProtocolQuery'
export { useProtocolAnalysesQuery } from './useProtocolAnalysesQuery'
export { useProtocolAnalysisAsDocumentQuery } from './useProtocolAnalysisAsDocumentQuery'
export { useCreateProtocolMutation } from './useCreateProtocolMutation'
export { useCreateProtocolAnalysisMutation } from './useCreateProtocolAnalysisMutation'
export { useDeleteProtocolMutation } from './useDeleteProtocolMutation'
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { createProtocolAnalysis } from '@opentrons/api-client'
import { useMutation, useQueryClient } from 'react-query'
import { useHost } from '../api'
import type {
ErrorResponse,
HostConfig,
RunTimeParameterCreateData,
} from '@opentrons/api-client'
import type { ProtocolAnalysisSummary } from '@opentrons/shared-data'
import type { AxiosError } from 'axios'
import type {
UseMutationResult,
UseMutationOptions,
UseMutateFunction,
} from 'react-query'

export interface CreateProtocolAnalysisVariables {
protocolKey: string
runTimeParameterValues?: RunTimeParameterCreateData
forceReAnalyze?: boolean
}
export type UseCreateProtocolMutationResult = UseMutationResult<
ProtocolAnalysisSummary[],
AxiosError<ErrorResponse>,
CreateProtocolAnalysisVariables
> & {
createProtocolAnalysis: UseMutateFunction<
ProtocolAnalysisSummary[],
AxiosError<ErrorResponse>,
CreateProtocolAnalysisVariables
>
}

export type UseCreateProtocolAnalysisMutationOptions = UseMutationOptions<
ProtocolAnalysisSummary[],
AxiosError<ErrorResponse>,
CreateProtocolAnalysisVariables
>

export function useCreateProtocolAnalysisMutation(
protocolId: string | null,
hostOverride?: HostConfig | null,
options: UseCreateProtocolAnalysisMutationOptions | undefined = {}
): UseCreateProtocolMutationResult {
const contextHost = useHost()
const host =
hostOverride != null ? { ...contextHost, ...hostOverride } : contextHost
const queryClient = useQueryClient()

const mutation = useMutation<
ProtocolAnalysisSummary[],
AxiosError<ErrorResponse>,
CreateProtocolAnalysisVariables
>(
[host, 'protocols', protocolId, 'analyses'],
({ protocolKey, runTimeParameterValues, forceReAnalyze }) =>
createProtocolAnalysis(
host as HostConfig,
protocolKey,
runTimeParameterValues,
forceReAnalyze
)
.then(response => {
queryClient
.invalidateQueries([host, 'protocols', protocolId, 'analyses'])
.then(() =>
queryClient.setQueryData(
[host, 'protocols', protocolId, 'analyses'],
response.data
)
)
.catch((e: Error) => {
throw e
})
return response.data
})
.catch((e: Error) => {
throw e
}),
options
)
return {
...mutation,
createProtocolAnalysis: mutation.mutate,
}
}

0 comments on commit 485bf0e

Please sign in to comment.