diff --git a/api-client/src/runs/constants.ts b/api-client/src/runs/constants.ts
new file mode 100644
index 00000000000..9f0d8293ef6
--- /dev/null
+++ b/api-client/src/runs/constants.ts
@@ -0,0 +1,11 @@
+import {
+ RUN_STATUS_FAILED,
+ RUN_STATUS_STOPPED,
+ RUN_STATUS_SUCCEEDED,
+} from './types'
+
+export const RUN_STATUSES_TERMINAL = [
+ RUN_STATUS_SUCCEEDED,
+ RUN_STATUS_FAILED,
+ RUN_STATUS_STOPPED,
+]
diff --git a/api-client/src/runs/index.ts b/api-client/src/runs/index.ts
index fa38dade02f..1d62755d4c5 100644
--- a/api-client/src/runs/index.ts
+++ b/api-client/src/runs/index.ts
@@ -10,6 +10,6 @@ export { getCommands } from './commands/getCommands'
export { createRunAction } from './createRunAction'
export * from './createLabwareOffset'
export * from './createLabwareDefinition'
-
+export * from './constants'
export * from './types'
export type { CreateRunData } from './createRun'
diff --git a/api-client/src/runs/types.ts b/api-client/src/runs/types.ts
index 7e6ec2b0ee7..36c5f9a3a20 100644
--- a/api-client/src/runs/types.ts
+++ b/api-client/src/runs/types.ts
@@ -4,6 +4,7 @@ import type {
LoadedPipette,
ModuleModel,
RunTimeCommand,
+ RunTimeParameter,
} from '@opentrons/shared-data'
import type { ResourceLink, ErrorDetails } from '../types'
export * from './commands/types'
@@ -47,7 +48,7 @@ export interface LegacyGoodRunData {
modules: LoadedModule[]
protocolId?: string
labwareOffsets?: LabwareOffset[]
- runTimeParameterValues?: RunTimeParameterCreateData
+ runTimeParameters: RunTimeParameter[]
}
export interface KnownGoodRunData extends LegacyGoodRunData {
diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolRunRunTimeParameters.tsx b/app/src/organisms/Devices/ProtocolRun/ProtocolRunRunTimeParameters.tsx
index ea7ec478415..6580a1fd7f1 100644
--- a/app/src/organisms/Devices/ProtocolRun/ProtocolRunRunTimeParameters.tsx
+++ b/app/src/organisms/Devices/ProtocolRun/ProtocolRunRunTimeParameters.tsx
@@ -1,6 +1,10 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import styled, { css } from 'styled-components'
+import {
+ RUN_STATUS_STOPPED,
+ RUN_STATUSES_TERMINAL,
+} from '@opentrons/api-client'
import { formatRunTimeParameterValue } from '@opentrons/shared-data'
import {
ALIGN_CENTER,
@@ -25,6 +29,8 @@ import { Tooltip } from '../../../atoms/Tooltip'
import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis'
import type { RunTimeParameter } from '@opentrons/shared-data'
+import { useRunStatus } from '../../RunTimeControl/hooks'
+import { useNotifyRunQuery } from '../../../resources/runs'
interface ProtocolRunRuntimeParametersProps {
runId: string
@@ -34,7 +40,15 @@ export function ProtocolRunRuntimeParameters({
}: ProtocolRunRuntimeParametersProps): JSX.Element {
const { t } = useTranslation('protocol_setup')
const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId)
- const runTimeParameters = mostRecentAnalysis?.runTimeParameters ?? []
+ const runStatus = useRunStatus(runId)
+ const isRunTerminal =
+ // @ts-expect-error: expected that runStatus may not be a terminal status
+ runStatus == null ? false : RUN_STATUSES_TERMINAL.includes(runStatus)
+ const run = useNotifyRunQuery(runId).data
+ const runTimeParameters =
+ (isRunTerminal
+ ? run?.data?.runTimeParameters
+ : mostRecentAnalysis?.runTimeParameters) ?? []
const hasParameter = runTimeParameters.length > 0
const hasCustomValues = runTimeParameters.some(
@@ -79,7 +93,11 @@ export function ProtocolRunRuntimeParameters({
{!hasParameter ? (
-
+
) : (
<>
diff --git a/app/src/organisms/InterventionModal/__fixtures__/index.ts b/app/src/organisms/InterventionModal/__fixtures__/index.ts
index b6d631f4c97..2611fe19b03 100644
--- a/app/src/organisms/InterventionModal/__fixtures__/index.ts
+++ b/app/src/organisms/InterventionModal/__fixtures__/index.ts
@@ -188,6 +188,7 @@ export const mockRunData: RunData = {
pipettes: [],
labware: [mockLabwareOnModule, mockLabwareOnSlot, mockLabwareOffDeck],
modules: [mockModule],
+ runTimeParameters: [],
}
export const mockLabwareRenderInfo = [
diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/__tests__/RecentRunProtocolCarousel.test.tsx b/app/src/organisms/OnDeviceDisplay/RobotDashboard/__tests__/RecentRunProtocolCarousel.test.tsx
index 85e956ed977..8bc3a481843 100644
--- a/app/src/organisms/OnDeviceDisplay/RobotDashboard/__tests__/RecentRunProtocolCarousel.test.tsx
+++ b/app/src/organisms/OnDeviceDisplay/RobotDashboard/__tests__/RecentRunProtocolCarousel.test.tsx
@@ -26,6 +26,7 @@ const mockRun = {
pipettes: [],
protocolId: 'mockSortedProtocolID',
status: 'stopped',
+ runTimeParameters: [],
}
const render = (
diff --git a/app/src/organisms/ProtocolUpload/hooks/useCloneRun.ts b/app/src/organisms/ProtocolUpload/hooks/useCloneRun.ts
index 0858544d93c..fe6e3ab3649 100644
--- a/app/src/organisms/ProtocolUpload/hooks/useCloneRun.ts
+++ b/app/src/organisms/ProtocolUpload/hooks/useCloneRun.ts
@@ -1,10 +1,13 @@
import { useQueryClient } from 'react-query'
-import { useHost, useCreateRunMutation } from '@opentrons/react-api-client'
-
+import {
+ useHost,
+ useCreateRunMutation,
+ useCreateProtocolAnalysisMutation,
+} from '@opentrons/react-api-client'
import { useNotifyRunQuery } from '../../../resources/runs'
-import type { Run } from '@opentrons/api-client'
+import type { Run, RunTimeParameterCreateData } from '@opentrons/api-client'
interface UseCloneRunResult {
cloneRun: () => void
@@ -13,28 +16,45 @@ interface UseCloneRunResult {
export function useCloneRun(
runId: string | null,
- onSuccessCallback?: (createRunResponse: Run) => unknown
+ onSuccessCallback?: (createRunResponse: Run) => unknown,
+ triggerAnalysis: boolean = false
): UseCloneRunResult {
const host = useHost()
const queryClient = useQueryClient()
const { data: runRecord } = useNotifyRunQuery(runId)
+ const protocolKey = runRecord?.data.protocolId ?? null
+
const { createRun, isLoading } = useCreateRunMutation({
onSuccess: response => {
- queryClient
- .invalidateQueries([host, 'runs'])
- .catch((e: Error) =>
- console.error(`error invalidating runs query: ${e.message}`)
- )
+ const invalidateRuns = queryClient.invalidateQueries([host, 'runs'])
+ const invalidateProtocols = queryClient.invalidateQueries([
+ host,
+ 'protocols',
+ protocolKey,
+ ])
+ Promise.all([invalidateRuns, invalidateProtocols]).catch((e: Error) =>
+ console.error(`error invalidating runs query: ${e.message}`)
+ )
if (onSuccessCallback != null) onSuccessCallback(response)
},
})
+ const { createProtocolAnalysis } = useCreateProtocolAnalysisMutation(
+ protocolKey,
+ host
+ )
const cloneRun = (): void => {
if (runRecord != null) {
- const {
- protocolId,
- labwareOffsets,
- runTimeParameterValues,
- } = runRecord.data
+ const { protocolId, labwareOffsets, runTimeParameters } = runRecord.data
+ const runTimeParameterValues = runTimeParameters.reduce(
+ (acc, param) =>
+ param.value !== param.default
+ ? { ...acc, [param.variableName]: param.value }
+ : acc,
+ {}
+ )
+ if (triggerAnalysis && protocolKey != null) {
+ createProtocolAnalysis({ protocolKey, runTimeParameterValues })
+ }
createRun({ protocolId, labwareOffsets, runTimeParameterValues })
} else {
console.info('failed to clone run record, source run record not found')
diff --git a/app/src/organisms/RunPreview/index.tsx b/app/src/organisms/RunPreview/index.tsx
index a75257c1952..48ea808064d 100644
--- a/app/src/organisms/RunPreview/index.tsx
+++ b/app/src/organisms/RunPreview/index.tsx
@@ -3,6 +3,8 @@ import { css } from 'styled-components'
import { useTranslation } from 'react-i18next'
import { ViewportList, ViewportListRef } from 'react-viewport-list'
+import { RUN_STATUSES_TERMINAL } from '@opentrons/api-client'
+import { useAllCommandsQuery } from '@opentrons/react-api-client'
import {
ALIGN_CENTER,
BORDERS,
@@ -24,6 +26,8 @@ import { CommandText } from '../CommandText'
import { Divider } from '../../atoms/structure'
import { NAV_BAR_WIDTH } from '../../App/constants'
import { CommandIcon } from './CommandIcon'
+import { useRunStatus } from '../RunTimeControl/hooks'
+
import type { RobotType } from '@opentrons/shared-data'
const COLOR_FADE_MS = 500
@@ -41,6 +45,13 @@ export const RunPreviewComponent = (
): JSX.Element | null => {
const { t } = useTranslation('run_details')
const robotSideAnalysis = useMostRecentCompletedAnalysis(runId)
+ const runStatus = useRunStatus(runId)
+ // @ts-expect-error: expected that runStatus may not be a terminal status
+ const isRunTerminal = RUN_STATUSES_TERMINAL.includes(runStatus)
+ const commandsFromQuery = useAllCommandsQuery(runId, null, {
+ staleTime: Infinity,
+ cacheTime: Infinity,
+ }).data?.data
const viewPortRef = React.useRef(null)
const currentRunCommandKey = useNotifyLastRunCommandKey(runId, {
refetchInterval: LIVE_RUN_COMMANDS_POLL_MS,
@@ -50,7 +61,9 @@ export const RunPreviewComponent = (
setIsCurrentCommandVisible,
] = React.useState(true)
if (robotSideAnalysis == null) return null
- const currentRunCommandIndex = robotSideAnalysis.commands.findIndex(
+ const commands =
+ (isRunTerminal ? commandsFromQuery : robotSideAnalysis.commands) ?? []
+ const currentRunCommandIndex = commands.findIndex(
c => c.key === currentRunCommandKey
)
@@ -69,7 +82,7 @@ export const RunPreviewComponent = (
{t('run_preview')}
- {t('steps_total', { count: robotSideAnalysis.commands.length })}
+ {t('steps_total', { count: commands.length })}
@@ -79,7 +92,7 @@ export const RunPreviewComponent = (
) : null}
- {currentRunCommandIndex === robotSideAnalysis.commands.length - 1 ? (
+ {currentRunCommandIndex === commands.length - 1 ? (
{t('end_of_protocol')}
diff --git a/app/src/organisms/RunTimeControl/__fixtures__/index.ts b/app/src/organisms/RunTimeControl/__fixtures__/index.ts
index 1a18a9a6bcf..33f2e0c4393 100644
--- a/app/src/organisms/RunTimeControl/__fixtures__/index.ts
+++ b/app/src/organisms/RunTimeControl/__fixtures__/index.ts
@@ -41,6 +41,7 @@ export const mockPausedRun: RunData = {
pipettes: [],
labware: [],
modules: [],
+ runTimeParameters: [],
}
export const mockPauseRequestedRun: RunData = {
@@ -65,6 +66,7 @@ export const mockPauseRequestedRun: RunData = {
pipettes: [],
labware: [],
modules: [],
+ runTimeParameters: [],
}
export const mockRunningRun: RunData = {
@@ -94,6 +96,7 @@ export const mockRunningRun: RunData = {
pipettes: [],
labware: [],
modules: [],
+ runTimeParameters: [],
}
export const mockFailedRun: RunData = {
@@ -133,6 +136,7 @@ export const mockFailedRun: RunData = {
pipettes: [],
labware: [],
modules: [],
+ runTimeParameters: [],
}
export const mockStopRequestedRun: RunData = {
@@ -167,6 +171,7 @@ export const mockStopRequestedRun: RunData = {
pipettes: [],
labware: [],
modules: [],
+ runTimeParameters: [],
}
export const mockStoppedRun: RunData = {
@@ -201,6 +206,7 @@ export const mockStoppedRun: RunData = {
pipettes: [],
labware: [],
modules: [],
+ runTimeParameters: [],
}
export const mockSucceededRun: RunData = {
@@ -230,6 +236,7 @@ export const mockSucceededRun: RunData = {
pipettes: [],
labware: [],
modules: [],
+ runTimeParameters: [],
}
export const mockIdleUnstartedRun: RunData = {
@@ -243,6 +250,7 @@ export const mockIdleUnstartedRun: RunData = {
pipettes: [],
labware: [],
modules: [],
+ runTimeParameters: [],
}
export const mockIdleStartedRun: RunData = {
@@ -272,6 +280,7 @@ export const mockIdleStartedRun: RunData = {
pipettes: [],
labware: [],
modules: [],
+ runTimeParameters: [],
}
export const mockCommand = {
diff --git a/app/src/organisms/RunTimeControl/hooks.ts b/app/src/organisms/RunTimeControl/hooks.ts
index db042a2ce65..fee33ed29c9 100644
--- a/app/src/organisms/RunTimeControl/hooks.ts
+++ b/app/src/organisms/RunTimeControl/hooks.ts
@@ -52,7 +52,8 @@ export function useRunControls(
const { cloneRun, isLoading: isResetRunLoading } = useCloneRun(
runId ?? null,
- onCloneRunSuccess
+ onCloneRunSuccess,
+ true
)
return {
diff --git a/components/src/molecules/ParametersTable/InfoScreen.tsx b/components/src/molecules/ParametersTable/InfoScreen.tsx
index b9798f828e3..9affc1472b6 100644
--- a/components/src/molecules/ParametersTable/InfoScreen.tsx
+++ b/components/src/molecules/ParametersTable/InfoScreen.tsx
@@ -8,14 +8,25 @@ import { Flex } from '../../primitives'
import { ALIGN_CENTER, DIRECTION_COLUMN } from '../../styles'
interface InfoScreenProps {
- contentType: 'parameters' | 'moduleControls'
+ contentType: 'parameters' | 'moduleControls' | 'runNotStarted'
}
export function InfoScreen({ contentType }: InfoScreenProps): JSX.Element {
- const bodyText =
- contentType === 'parameters'
- ? 'No parameters specified in this protocol'
- : 'Connect modules to see controls'
+ let bodyText: string = ''
+ switch (contentType) {
+ case 'parameters':
+ bodyText = 'No parameters specified in this protocol'
+ break
+ case 'moduleControls':
+ bodyText = 'Connect modules to see controls'
+ break
+ case 'runNotStarted':
+ bodyText = 'Run was never started'
+ break
+ default:
+ bodyText = contentType
+ }
+
return (
(
runId: string | null,
- params: GetCommandsParams = DEFAULT_PARAMS,
+ params?: GetCommandsParams | null,
options: UseQueryOptions = {}
): UseQueryResult {
const host = useHost()
+ const nullCheckedParams = params ?? DEFAULT_PARAMS
+
const allOptions: UseQueryOptions = {
...options,
enabled: host !== null && runId != null && options.enabled !== false,
}
- const { cursor, pageLength } = params
+ const { cursor, pageLength } = nullCheckedParams
const query = useQuery(
[host, 'runs', runId, 'commands', cursor, pageLength],
() => {
- return getCommands(host as HostConfig, runId as string, params).then(
- response => response.data
- )
+ return getCommands(
+ host as HostConfig,
+ runId as string,
+ nullCheckedParams
+ ).then(response => response.data)
},
allOptions
)