Skip to content

Commit

Permalink
refactor(app, api-client, shared-data): Set command intent to "fixit" (
Browse files Browse the repository at this point in the history
…#15119)

Closes EXEC-431

Before we can POST fixit commands during Error Recovery flows, we need to refactor useChainRunCommands to support sending the params necessary to POST a fixit command. In addition to a normal CreateCommand, this requires sending:

* A failedCommandId
* An explicit fixit intent
  • Loading branch information
mjhuff authored May 8, 2024
1 parent 5f390fc commit 0e7b29a
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 32 deletions.
1 change: 1 addition & 0 deletions api-client/src/runs/commands/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface CommandsAsPreSerializedListData {
export interface CreateCommandParams {
waitUntilComplete?: boolean
timeout?: number
failedCommandId?: string
}

export interface RunCommandError {
Expand Down
21 changes: 20 additions & 1 deletion app/src/resources/runs/__tests__/util.test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand All @@ -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')
})
})
33 changes: 26 additions & 7 deletions app/src/resources/runs/hooks.ts
Original file line number Diff line number Diff line change
@@ -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<
Expand All @@ -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[],
Expand All @@ -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[],
Expand Down Expand Up @@ -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
Expand Down
36 changes: 29 additions & 7 deletions app/src/resources/runs/utils.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
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[],
createRunCommand: CreateRunCommand,
continuePastCommandFailure: boolean = true,
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>
): Promise<CommandData[]> => {
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,
Expand Down Expand Up @@ -57,9 +60,11 @@ export const chainLiveCommandsRecursive = (
continuePastCommandFailure: boolean = true,
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>
): Promise<CommandData[]> => {
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,
Expand Down Expand Up @@ -98,9 +103,11 @@ export const chainMaintenanceCommandsRecursive = (
continuePastCommandFailure: boolean = true,
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>
): Promise<CommandData[]> => {
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],
Expand Down Expand Up @@ -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
}
22 changes: 7 additions & 15 deletions react-api-client/src/runs/useCreateCommandMutation.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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<CommandData, unknown, CreateCommandMutateParams>(
({ 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'])
Expand All @@ -55,6 +46,7 @@ export function useCreateCommandMutation(): UseCreateCommandMutationResult {
)
return response.data
})
}
)

return {
Expand Down
4 changes: 3 additions & 1 deletion shared-data/command/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 }
}
Expand Down
3 changes: 2 additions & 1 deletion shared-data/protocol/types/schemaV6/command/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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
Expand Down

0 comments on commit 0e7b29a

Please sign in to comment.