diff --git a/api-client/src/runs/commands/types.ts b/api-client/src/runs/commands/types.ts index d0b443b297a..a984ae70366 100644 --- a/api-client/src/runs/commands/types.ts +++ b/api-client/src/runs/commands/types.ts @@ -43,6 +43,7 @@ export interface CommandsAsPreSerializedListData { export interface CreateCommandParams { waitUntilComplete?: boolean timeout?: number + failedCommandId?: string } export interface RunCommandError { diff --git a/app/src/resources/runs/__tests__/util.test.ts b/app/src/resources/runs/__tests__/util.test.ts index 2c86d41ffda..40bdb92a0f3 100644 --- a/app/src/resources/runs/__tests__/util.test.ts +++ b/app/src/resources/runs/__tests__/util.test.ts @@ -1,5 +1,7 @@ import { describe, it, expect } from 'vitest' -import { formatTimeWithUtcLabel } from '../utils' +import { formatTimeWithUtcLabel, setCommandIntent } from '../utils' + +import type { CreateCommand } from '@opentrons/shared-data' describe('formatTimeWithUtc', () => { it('return formatted time with UTC', () => { @@ -21,3 +23,20 @@ describe('formatTimeWithUtc', () => { expect(result).toEqual('unknown') }) }) + +const mockCommand = { + commandType: 'home', + params: {}, + intent: 'protocol', +} as CreateCommand + +describe('setCommandIntent', () => { + it('explicitly sets the command intent to "fixit" if a failedCommandId is specified', () => { + const commandWithFixitIntent = setCommandIntent(mockCommand, 'MOCK_ID') + expect(commandWithFixitIntent.intent).toEqual('fixit') + }) + it('does not modify the command intent if no failedCommandId is specified', () => { + const command = setCommandIntent(mockCommand) + expect(command.intent).toEqual('protocol') + }) +}) diff --git a/app/src/resources/runs/hooks.ts b/app/src/resources/runs/hooks.ts index 528219776a4..5b41edd6d4c 100644 --- a/app/src/resources/runs/hooks.ts +++ b/app/src/resources/runs/hooks.ts @@ -1,25 +1,29 @@ import * as React from 'react' import { useSelector } from 'react-redux' -import type { CreateCommand } from '@opentrons/shared-data' -import type { HostConfig } from '@opentrons/api-client' + import { useCreateCommandMutation, useCreateLiveCommandMutation, useCreateMaintenanceCommandMutation, useCreateMaintenanceRunMutation, } from '@opentrons/react-api-client' + import { chainRunCommandsRecursive, chainMaintenanceCommandsRecursive, chainLiveCommandsRecursive, + setCommandIntent, } from './utils' import { getIsOnDevice } from '../../redux/config' import { useMaintenanceRunTakeover } from '../../organisms/TakeoverModal' + import type { UseCreateMaintenanceRunMutationOptions, UseCreateMaintenanceRunMutationResult, CreateMaintenanceRunType, } from '@opentrons/react-api-client' +import type { CreateCommand } from '@opentrons/shared-data' +import type { HostConfig } from '@opentrons/api-client' import type { ModulePrepCommandsType } from '../../organisms/Devices/getModulePrepCommands' export type CreateCommandMutate = ReturnType< @@ -40,18 +44,28 @@ type CreateRunCommandMutation = Omit< > & { createRunCommand: CreateRunCommand } export function useCreateRunCommandMutation( - runId: string + runId: string, + failedCommandId?: string ): CreateRunCommandMutation { const createCommandMutation = useCreateCommandMutation() + return { ...createCommandMutation, - createRunCommand: (variables, ...options) => - createCommandMutation.createCommand({ ...variables, runId }, ...options), + createRunCommand: (variables, ...options) => { + const { command } = variables + const commandWithIntent = setCommandIntent(command, failedCommandId) + + return createCommandMutation.createCommand( + { ...variables, runId, command: commandWithIntent, failedCommandId }, + ...options + ) + }, } } export function useChainRunCommands( - runId: string + runId: string, + failedCommandId?: string ): { chainRunCommands: ( commands: CreateCommand[], @@ -60,7 +74,11 @@ export function useChainRunCommands( isCommandMutationLoading: boolean } { const [isLoading, setIsLoading] = React.useState(false) - const { createRunCommand } = useCreateRunCommandMutation(runId) + + const { createRunCommand } = useCreateRunCommandMutation( + runId, + failedCommandId + ) return { chainRunCommands: ( commands: CreateCommand[], @@ -131,6 +149,7 @@ type CreateTargetedMaintenanceRunMutation = UseCreateMaintenanceRunMutationResul createTargetedMaintenanceRun: CreateMaintenanceRunType } +// A wrapper around useCreateMaintenanceRunMutation that ensures the ODD TakeoverModal renders, if applicable. export function useCreateTargetedMaintenanceRunMutation( options: UseCreateMaintenanceRunMutationOptions = {}, hostOverride?: HostConfig | null diff --git a/app/src/resources/runs/utils.ts b/app/src/resources/runs/utils.ts index 1827a58ef8f..596b2ccba29 100644 --- a/app/src/resources/runs/utils.ts +++ b/app/src/resources/runs/utils.ts @@ -1,11 +1,12 @@ -import * as React from 'react' import { format } from 'date-fns' + +import type * as React from 'react' +import type { UseMutateAsyncFunction } from 'react-query' import type { CommandData } from '@opentrons/api-client' import type { CreateCommand } from '@opentrons/shared-data' +import type { CreateLiveCommandMutateParams } from '@opentrons/react-api-client/src/runs/useCreateLiveCommandMutation' +import type { ModulePrepCommandsType } from '../../organisms/Devices/getModulePrepCommands' import type { CreateMaintenanceCommand, CreateRunCommand } from './hooks' -import type { UseMutateAsyncFunction } from 'react-query' -import { CreateLiveCommandMutateParams } from '@opentrons/react-api-client/src/runs/useCreateLiveCommandMutation' -import { ModulePrepCommandsType } from '../../organisms/Devices/getModulePrepCommands' export const chainRunCommandsRecursive = ( commands: CreateCommand[], @@ -13,9 +14,11 @@ export const chainRunCommandsRecursive = ( continuePastCommandFailure: boolean = true, setIsLoading: React.Dispatch> ): Promise => { - if (commands.length < 1) + if (commands.length < 1) { return Promise.reject(new Error('no commands to execute')) + } setIsLoading(true) + return createRunCommand({ command: commands[0], waitUntilComplete: true, @@ -57,9 +60,11 @@ export const chainLiveCommandsRecursive = ( continuePastCommandFailure: boolean = true, setIsLoading: React.Dispatch> ): Promise => { - if (commands.length < 1) + if (commands.length < 1) { return Promise.reject(new Error('no commands to execute')) + } setIsLoading(true) + return createLiveCommand({ command: commands[0], waitUntilComplete: true, @@ -98,9 +103,11 @@ export const chainMaintenanceCommandsRecursive = ( continuePastCommandFailure: boolean = true, setIsLoading: React.Dispatch> ): Promise => { - if (commands.length < 1) + if (commands.length < 1) { return Promise.reject(new Error('no commands to execute')) + } setIsLoading(true) + return createMaintenanceCommand({ maintenanceRunId: maintenanceRunId, command: commands[0], @@ -145,3 +152,18 @@ export const formatTimeWithUtcLabel = (time: string | null): string => { ? `${format(new Date(time), 'M/d/yy HH:mm')} ${UTC_LABEL}` : `${time} ${UTC_LABEL}` } + +// Visit the command, setting the command intent to "fixit" if a failedCommandId is supplied. +export const setCommandIntent = ( + command: CreateCommand, + failedCommandId?: string +): CreateCommand => { + const isCommandWithFixitIntent = failedCommandId != null + if (isCommandWithFixitIntent) { + return { + ...command, + intent: 'fixit', + } + } + return command +} diff --git a/react-api-client/src/runs/useCreateCommandMutation.ts b/react-api-client/src/runs/useCreateCommandMutation.ts index aed58129cd7..3b0fc5f1b56 100644 --- a/react-api-client/src/runs/useCreateCommandMutation.ts +++ b/react-api-client/src/runs/useCreateCommandMutation.ts @@ -1,11 +1,7 @@ import { useMutation, useQueryClient } from 'react-query' import { createCommand } from '@opentrons/api-client' import { useHost } from '../api' -import type { - UseMutationResult, - UseMutationOptions, - UseMutateAsyncFunction, -} from 'react-query' +import type { UseMutationResult, UseMutateAsyncFunction } from 'react-query' import type { CommandData, HostConfig, @@ -32,21 +28,16 @@ export type UseCreateCommandMutationResult = UseMutationResult< > } -export type UseCreateCommandMutationOptions = UseMutationOptions< - CommandData, - unknown, - CreateCommandMutateParams -> - export function useCreateCommandMutation(): UseCreateCommandMutationResult { const host = useHost() const queryClient = useQueryClient() const mutation = useMutation( - ({ runId, command, waitUntilComplete, timeout }) => - createCommand(host as HostConfig, runId, command, { - waitUntilComplete, - timeout, + params => { + const { runId, command, ...rest } = params + + return createCommand(host as HostConfig, runId, command, { + ...rest, }).then(response => { queryClient .invalidateQueries([host, 'runs']) @@ -55,6 +46,7 @@ export function useCreateCommandMutation(): UseCreateCommandMutationResult { ) return response.data }) + } ) return { diff --git a/shared-data/command/types/index.ts b/shared-data/command/types/index.ts index 980eb8fb124..e6553fd7025 100644 --- a/shared-data/command/types/index.ts +++ b/shared-data/command/types/index.ts @@ -38,6 +38,7 @@ export interface CommandNote { source: string } export type CommandStatus = 'queued' | 'running' | 'succeeded' | 'failed' +export type CommandIntent = 'protocol' | 'setup' | 'fixit' export interface CommonCommandRunTimeInfo { key?: string id: string @@ -46,10 +47,11 @@ export interface CommonCommandRunTimeInfo { createdAt: string startedAt: string | null completedAt: string | null - intent?: 'protocol' | 'setup' + intent?: CommandIntent notes?: CommandNote[] | null } export interface CommonCommandCreateInfo { + intent?: CommandIntent key?: string meta?: { [key: string]: any } } diff --git a/shared-data/protocol/types/schemaV6/command/index.ts b/shared-data/protocol/types/schemaV6/command/index.ts index 705e551b9a0..e58794afc50 100644 --- a/shared-data/protocol/types/schemaV6/command/index.ts +++ b/shared-data/protocol/types/schemaV6/command/index.ts @@ -6,6 +6,7 @@ import type { GantryRunTimeCommand, GantryCreateCommand } from './gantry' import type { ModuleRunTimeCommand, ModuleCreateCommand } from './module' import type { SetupRunTimeCommand, SetupCreateCommand } from './setup' import type { TimingRunTimeCommand, TimingCreateCommand } from './timing' +import type { CommandIntent } from '../../../../command' export * from './pipetting' export * from './gantry' @@ -26,7 +27,7 @@ export interface CommonCommandRunTimeInfo { createdAt: string startedAt: string | null completedAt: string | null - intent?: 'protocol' | 'setup' + intent?: CommandIntent } export interface CommonCommandCreateInfo { key?: string