Skip to content

Commit

Permalink
fix(app, react-api-client): handle failed analysis for RTP protocol o…
Browse files Browse the repository at this point in the history
…n ODD (#15101)

closes RQA-2673
  • Loading branch information
ncdiehl11 authored May 7, 2024
1 parent ef946ef commit 9ae6051
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type { ModalHeaderBaseProps } from '../../molecules/Modal/types'

interface AnalysisFailedModalProps {
errors: string[]
protocolId: string
protocolId: string | null
setShowAnalysisFailedModal: (showAnalysisFailedModal: boolean) => void
}

Expand All @@ -36,7 +36,7 @@ export function AnalysisFailedModal({
}

const handleRestartSetup = (): void => {
history.push(`/protocols/${protocolId}`)
history.push(protocolId != null ? `/protocols/${protocolId}` : '/protocols')
}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,10 @@ describe('AnalysisFailedModal', () => {
fireEvent.click(screen.getByText('Restart setup'))
expect(mockPush).toHaveBeenCalledWith(`/protocols/${PROTOCOL_ID}`)
})

it('should push to protocols dashboard when tapping restart setup button and protocol ID is null', () => {
render({ ...props, protocolId: null })
fireEvent.click(screen.getByText('Restart setup'))
expect(mockPush).toHaveBeenCalledWith('/protocols')
})
})
1 change: 1 addition & 0 deletions app/src/organisms/ProtocolSetupParameters/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ export function ProtocolSetupParameters({
detail={formatRunTimeParameterValue(parameter, t)}
description={parameter.description}
fontSize="h4"
disabled={startSetup}
/>
</React.Fragment>
)
Expand Down
29 changes: 23 additions & 6 deletions app/src/pages/ProtocolDashboard/ProtocolCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from '@opentrons/components'
import {
useHost,
useMostRecentSuccessfulAnalysisAsDocumentQuery,
useProtocolAnalysisAsDocumentQuery,
} from '@opentrons/react-api-client'
import { deleteProtocol, deleteRun, getProtocol } from '@opentrons/api-client'
Expand Down Expand Up @@ -66,8 +67,20 @@ export function ProtocolCard(props: {
const queryClient = useQueryClient()
const host = useHost()

const { id: protocolId, analysisSummaries } = protocol
const {
data: mostRecentSuccessfulAnalysis,
} = useMostRecentSuccessfulAnalysisAsDocumentQuery(
protocolId,
analysisSummaries,
{
enabled: protocol != null,
refetchInterval: analysisData =>
analysisData == null ? REFETCH_INTERVAL : false,
}
)
const { data: mostRecentAnalysis } = useProtocolAnalysisAsDocumentQuery(
protocol.id,
protocolId,
last(protocol.analysisSummaries)?.id ?? null,
{
enabled: protocol != null,
Expand All @@ -76,14 +89,18 @@ export function ProtocolCard(props: {
}
)

const analysisForProtocolCard =
mostRecentSuccessfulAnalysis == null
? mostRecentAnalysis
: mostRecentSuccessfulAnalysis
const isFailedAnalysis =
(mostRecentAnalysis != null &&
'result' in mostRecentAnalysis &&
(mostRecentAnalysis.result === 'error' ||
mostRecentAnalysis.result === 'not-ok')) ??
(analysisForProtocolCard != null &&
'result' in analysisForProtocolCard &&
(analysisForProtocolCard.result === 'error' ||
analysisForProtocolCard.result === 'not-ok')) ??
false

const isPendingAnalysis = mostRecentAnalysis == null
const isPendingAnalysis = analysisForProtocolCard == null

const handleProtocolClick = (
longpress: UseLongPressResult,
Expand Down
17 changes: 16 additions & 1 deletion app/src/pages/ProtocolDashboard/__tests__/ProtocolCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { vi, it, describe, expect, beforeEach } from 'vitest'
import { act, fireEvent, screen } from '@testing-library/react'
import { MemoryRouter } from 'react-router-dom'
import { UseQueryResult } from 'react-query'
import { useProtocolAnalysisAsDocumentQuery } from '@opentrons/react-api-client'
import {
useMostRecentSuccessfulAnalysisAsDocumentQuery,
useProtocolAnalysisAsDocumentQuery,
} from '@opentrons/react-api-client'

import { renderWithProviders } from '../../../__testing-utils__'
import { i18n } from '../../../i18n'
Expand Down Expand Up @@ -69,6 +72,9 @@ describe('ProtocolCard', () => {
vi.mocked(useProtocolAnalysisAsDocumentQuery).mockReturnValue({
data: { result: 'ok' } as any,
} as UseQueryResult<CompletedProtocolAnalysis>)
vi.mocked(useMostRecentSuccessfulAnalysisAsDocumentQuery).mockReturnValue({
data: { result: 'ok' } as any,
} as UseQueryResult<CompletedProtocolAnalysis>)
})
it('should redirect to protocol details after short click', () => {
render()
Expand All @@ -81,6 +87,9 @@ describe('ProtocolCard', () => {
vi.mocked(useProtocolAnalysisAsDocumentQuery).mockReturnValue({
data: { result: 'error' } as any,
} as UseQueryResult<CompletedProtocolAnalysis>)
vi.mocked(useMostRecentSuccessfulAnalysisAsDocumentQuery).mockReturnValue({
data: { result: 'not-ok', errors: ['some analysis error'] } as any,
} as UseQueryResult<CompletedProtocolAnalysis>)
render()
screen.getByLabelText('failedAnalysis_icon')
screen.getByText('Failed analysis')
Expand Down Expand Up @@ -115,6 +124,9 @@ describe('ProtocolCard', () => {
vi.mocked(useProtocolAnalysisAsDocumentQuery).mockReturnValue({
data: { result: 'error' } as any,
} as UseQueryResult<CompletedProtocolAnalysis>)
vi.mocked(useMostRecentSuccessfulAnalysisAsDocumentQuery).mockReturnValue({
data: { result: 'not-ok', errors: ['some analysis error'] } as any,
} as UseQueryResult<CompletedProtocolAnalysis>)
render()
const name = screen.getByText('yay mock protocol')
fireEvent.mouseDown(name)
Expand All @@ -136,6 +148,9 @@ describe('ProtocolCard', () => {
vi.mocked(useProtocolAnalysisAsDocumentQuery).mockReturnValue({
data: null as any,
} as UseQueryResult<CompletedProtocolAnalysis>)
vi.mocked(useMostRecentSuccessfulAnalysisAsDocumentQuery).mockReturnValue({
data: null as any,
} as UseQueryResult<CompletedProtocolAnalysis>)
render()
const name = screen.getByText('yay mock protocol')
fireEvent.mouseDown(name)
Expand Down
4 changes: 4 additions & 0 deletions app/src/pages/ProtocolSetup/__tests__/ProtocolSetup.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
useAttachedModules,
useLPCDisabledReason,
useModuleCalibrationStatus,
useProtocolAnalysisErrors,
useRobotType,
useRunCreatedAtTimestamp,
useTrackProtocolRunEvent,
Expand Down Expand Up @@ -248,6 +249,9 @@ describe('ProtocolSetup', () => {
},
},
} as any)
when(vi.mocked(useProtocolAnalysisErrors))
.calledWith(RUN_ID)
.thenReturn({ analysisErrors: null })
when(vi.mocked(useProtocolQuery))
.calledWith(PROTOCOL_ID, { staleTime: Infinity })
.thenReturn({
Expand Down
22 changes: 21 additions & 1 deletion app/src/pages/ProtocolSetup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
useAttachedModules,
useLPCDisabledReason,
useModuleCalibrationStatus,
useProtocolAnalysisErrors,
useRobotAnalyticsData,
useRobotType,
useTrackProtocolRunEvent,
Expand All @@ -64,6 +65,7 @@ import { ProtocolSetupDeckConfiguration } from '../../organisms/ProtocolSetupDec
import { useLaunchLPC } from '../../organisms/LabwarePositionCheck/useLaunchLPC'
import { getUnmatchedModulesForProtocol } from '../../organisms/ProtocolSetupModulesAndDeck/utils'
import { ConfirmCancelRunModal } from '../../organisms/OnDeviceDisplay/RunningProtocol'
import { AnalysisFailedModal } from '../../organisms/ProtocolSetupParameters/AnalysisFailedModal'
import {
getIncompleteInstrumentCount,
getProtocolUsesGripper,
Expand All @@ -90,6 +92,7 @@ import { getRequiredDeckConfig } from '../../resources/deck_configuration/utils'
import { useNotifyRunQuery } from '../../resources/runs'
import { ViewOnlyParameters } from '../../organisms/ProtocolSetupParameters/ViewOnlyParameters'

import type { Run } from '@opentrons/api-client'
import type { CutoutFixtureId, CutoutId } from '@opentrons/shared-data'
import type { OnDeviceRouteParams } from '../../App/types'
import type {
Expand Down Expand Up @@ -251,6 +254,7 @@ interface PrepareToRunProps {
confirmAttachment: () => void
play: () => void
robotName: string
runRecord: Run | null
}

function PrepareToRun({
Expand All @@ -259,6 +263,7 @@ function PrepareToRun({
confirmAttachment,
play,
robotName,
runRecord,
}: PrepareToRunProps): JSX.Element {
const { t, i18n } = useTranslation(['protocol_setup', 'shared'])
const history = useHistory()
Expand All @@ -272,7 +277,6 @@ function PrepareToRun({
observer.observe(scrollRef.current)
}

const { data: runRecord } = useNotifyRunQuery(runId, { staleTime: Infinity })
const protocolId = runRecord?.data?.protocolId ?? null
const { data: protocolRecord } = useProtocolQuery(protocolId, {
staleTime: Infinity,
Expand Down Expand Up @@ -797,11 +801,17 @@ export type SetupScreens =

export function ProtocolSetup(): JSX.Element {
const { runId } = useParams<OnDeviceRouteParams>()
const { data: runRecord } = useNotifyRunQuery(runId, { staleTime: Infinity })
const { analysisErrors } = useProtocolAnalysisErrors(runId)
const localRobot = useSelector(getLocalRobot)
const robotSerialNumber =
localRobot?.status != null ? getRobotSerialNumber(localRobot) : null
const trackEvent = useTrackEvent()
const { play } = useRunControls(runId)
const [
showAnalysisFailedModal,
setShowAnalysisFailedModal,
] = React.useState<boolean>(true)

const handleProceedToRunClick = (): void => {
trackEvent({
Expand Down Expand Up @@ -838,6 +848,7 @@ export function ProtocolSetup(): JSX.Element {
confirmAttachment={confirmAttachment}
play={play}
robotName={localRobot?.name != null ? localRobot.name : 'no name'}
runRecord={runRecord ?? null}
/>
),
instruments: (
Expand Down Expand Up @@ -872,6 +883,15 @@ export function ProtocolSetup(): JSX.Element {

return (
<>
{showAnalysisFailedModal &&
analysisErrors != null &&
analysisErrors?.length > 0 ? (
<AnalysisFailedModal
setShowAnalysisFailedModal={setShowAnalysisFailedModal}
protocolId={runRecord?.data.protocolId ?? null}
errors={analysisErrors.map(error => error.detail)}
/>
) : null}
{showConfirmationModal ? (
<ConfirmAttachedModal
onCloseClick={cancelExit}
Expand Down
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 @@ -2,6 +2,7 @@ export { useAllProtocolsQuery } from './useAllProtocolsQuery'
export { useAllProtocolIdsQuery } from './useAllProtocolIdsQuery'
export { useProtocolQuery } from './useProtocolQuery'
export { useProtocolAnalysesQuery } from './useProtocolAnalysesQuery'
export { useMostRecentSuccessfulAnalysisAsDocumentQuery } from './useMostRecentSuccessfulAnalysisAsDocumentQuery'
export { useProtocolAnalysisAsDocumentQuery } from './useProtocolAnalysisAsDocumentQuery'
export { useCreateProtocolMutation } from './useCreateProtocolMutation'
export { useCreateProtocolAnalysisMutation } from './useCreateProtocolAnalysisMutation'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useQuery } from 'react-query'
import { getProtocolAnalysisAsDocument } from '@opentrons/api-client'
import { useHost } from '../api'

import type { UseQueryResult, UseQueryOptions } from 'react-query'
import type { HostConfig } from '@opentrons/api-client'
import type {
CompletedProtocolAnalysis,
ProtocolAnalysisSummary,
} from '@opentrons/shared-data'

const getMostRecentSuccessfulAnalysisId = async (
analysisSummaryIds: string[],
host: HostConfig | null,
protocolId: string
): Promise<CompletedProtocolAnalysis | null> => {
for (const analysisId of analysisSummaryIds) {
const { data: analysis } = await getProtocolAnalysisAsDocument(
host as HostConfig,
protocolId,
analysisId
)
if (analysis.errors.length === 0) {
return analysis
}
}
return null
}

export function useMostRecentSuccessfulAnalysisAsDocumentQuery<TError = Error>(
protocolId: string,
analysisSummaries: ProtocolAnalysisSummary[],
options: UseQueryOptions<CompletedProtocolAnalysis | null, TError> = {}
): UseQueryResult<CompletedProtocolAnalysis | null, TError> {
const host = useHost()

const query = useQuery<CompletedProtocolAnalysis | null, TError>(
[host, 'protocols', protocolId, 'analyses', 'mostRecentSuccessful'],
async () => {
const analysisIds = analysisSummaries.map(summary => summary.id)

const mostRecentSuccessfulAnalysis = await getMostRecentSuccessfulAnalysisId(
analysisIds,
host,
protocolId
)

return mostRecentSuccessfulAnalysis
},
options
)

return query
}

0 comments on commit 9ae6051

Please sign in to comment.