diff --git a/app/src/resources/runs/__tests__/useCloneRun.test.tsx b/app/src/resources/runs/__tests__/useCloneRun.test.tsx index cf9de675f67..87971872ede 100644 --- a/app/src/resources/runs/__tests__/useCloneRun.test.tsx +++ b/app/src/resources/runs/__tests__/useCloneRun.test.tsx @@ -13,7 +13,7 @@ import { useCloneRun } from '../useCloneRun' import { useNotifyRunQuery } from '../useNotifyRunQuery' import type { FunctionComponent, ReactNode } from 'react' -import type { HostConfig } from '@opentrons/api-client' +import type { HostConfig, LabwareOffset } from '@opentrons/api-client' vi.mock('@opentrons/react-api-client') vi.mock('/app/resources/runs/useNotifyRunQuery') @@ -21,6 +21,7 @@ vi.mock('/app/resources/runs/useNotifyRunQuery') const HOST_CONFIG: HostConfig = { hostname: 'localhost' } const RUN_ID_NO_RTP: string = 'run_id_no_rtp' const RUN_ID_RTP: string = 'run_id_rtp' +const RUN_ID_DUPLICATE_OFFSETS: string = 'run_id_duplicate_offsets' describe('useCloneRun hook', () => { let wrapper: FunctionComponent<{ children: ReactNode }> @@ -34,7 +35,13 @@ describe('useCloneRun hook', () => { data: { id: RUN_ID_NO_RTP, protocolId: 'protocolId', - labwareOffsets: 'someOffset', + labwareOffsets: [ + { + definitionUri: 'uri1', + locationSequence: [1, 2], + offset: { x: 1, y: 1, z: 1 }, + }, + ], runTimeParameters: [ { type: 'int', @@ -59,7 +66,13 @@ describe('useCloneRun hook', () => { data: { id: RUN_ID_RTP, protocolId: 'protocolId', - labwareOffsets: 'someOffset', + labwareOffsets: [ + { + definitionUri: 'uri1', + locationSequence: [1, 2], + offset: { x: 1, y: 1, z: 1 }, + }, + ], runTimeParameters: [ { type: 'int', @@ -82,6 +95,38 @@ describe('useCloneRun hook', () => { }, }, } as any) + + const duplicateOffsets: LabwareOffset[] = [ + { + definitionUri: 'uri1', + locationSequence: [1, 2], + offset: { x: 1, y: 1, z: 1 }, + }, + { + definitionUri: 'uri2', + locationSequence: [3, 4], + offset: { x: 2, y: 2, z: 2 }, + }, + { + definitionUri: 'uri1', + locationSequence: [1, 2], + offset: { x: 3, y: 3, z: 3 }, + }, + ] as any + + when(vi.mocked(useNotifyRunQuery)) + .calledWith(RUN_ID_DUPLICATE_OFFSETS) + .thenReturn({ + data: { + data: { + id: RUN_ID_DUPLICATE_OFFSETS, + protocolId: 'protocolId', + labwareOffsets: duplicateOffsets, + runTimeParameters: [], + }, + }, + } as any) + when(vi.mocked(useCreateRunMutation)) .calledWith(expect.anything()) .thenReturn({ createRun: vi.fn() } as any) @@ -111,11 +156,18 @@ describe('useCloneRun hook', () => { result.current && result.current.cloneRun() expect(mockCreateRun).toHaveBeenCalledWith({ protocolId: 'protocolId', - labwareOffsets: 'someOffset', + labwareOffsets: [ + { + definitionUri: 'uri1', + locationSequence: [1, 2], + offset: { x: 1, y: 1, z: 1 }, + }, + ], runTimeParameterValues: {}, runTimeParameterFiles: {}, }) }) + it('should return a function that when called, calls createRun run with runTimeParameterValues overrides', async () => { const mockCreateRun = vi.fn() vi.mocked(useCreateRunMutation).mockReturnValue({ @@ -126,7 +178,74 @@ describe('useCloneRun hook', () => { result.current && result.current.cloneRun() expect(mockCreateRun).toHaveBeenCalledWith({ protocolId: 'protocolId', - labwareOffsets: 'someOffset', + labwareOffsets: [ + { + definitionUri: 'uri1', + locationSequence: [1, 2], + offset: { x: 1, y: 1, z: 1 }, + }, + ], + runTimeParameterValues: { + number_param: 2, + boolean_param: false, + }, + runTimeParameterFiles: { + file_param: 'fileId_123', + }, + }) + }) + + it('should filter duplicate labware offsets and keep only the most recent ones', async () => { + const mockCreateRun = vi.fn() + vi.mocked(useCreateRunMutation).mockReturnValue({ + createRun: mockCreateRun, + } as any) + + const { result } = renderHook(() => useCloneRun(RUN_ID_DUPLICATE_OFFSETS), { + wrapper, + }) + result.current && result.current.cloneRun() + + const expectedOffsets = [ + { + definitionUri: 'uri2', + locationSequence: [3, 4], + offset: { x: 2, y: 2, z: 2 }, + }, + { + definitionUri: 'uri1', + locationSequence: [1, 2], + offset: { x: 3, y: 3, z: 3 }, + }, + ] + + expect(mockCreateRun).toHaveBeenCalledWith({ + protocolId: 'protocolId', + labwareOffsets: expectedOffsets, + runTimeParameterValues: {}, + runTimeParameterFiles: {}, + }) + }) + + it('should handle analysis trigger when specified', async () => { + const mockCreateRun = vi.fn() + const mockCreateProtocolAnalysis = vi.fn() + + vi.mocked(useCreateRunMutation).mockReturnValue({ + createRun: mockCreateRun, + } as any) + vi.mocked(useCreateProtocolAnalysisMutation).mockReturnValue({ + createProtocolAnalysis: mockCreateProtocolAnalysis, + } as any) + + const { result } = renderHook( + () => useCloneRun(RUN_ID_RTP, undefined, true), + { wrapper } + ) + result.current && result.current.cloneRun() + + expect(mockCreateProtocolAnalysis).toHaveBeenCalledWith({ + protocolKey: 'protocolId', runTimeParameterValues: { number_param: 2, boolean_param: false, diff --git a/app/src/resources/runs/useCloneRun.ts b/app/src/resources/runs/useCloneRun.ts index 45f3a7a2c67..5dd3fdc9a4c 100644 --- a/app/src/resources/runs/useCloneRun.ts +++ b/app/src/resources/runs/useCloneRun.ts @@ -11,7 +11,8 @@ import { getRunTimeParameterFilesForRun, } from '/app/transformations/runs' -import type { Run } from '@opentrons/api-client' +import type { LabwareOffset, Run } from '@opentrons/api-client' +import isEqual from 'lodash/isEqual' interface UseCloneRunResult { cloneRun: () => void @@ -70,7 +71,7 @@ export function useCloneRun( } createRun({ protocolId, - labwareOffsets, + labwareOffsets: mostRecentUniqueLabwareOffsets(labwareOffsets), runTimeParameterValues, runTimeParameterFiles, }) @@ -81,3 +82,19 @@ export function useCloneRun( return { cloneRun, isLoadingRun, isCloning } } + +// Returns the most recent, unique offsets for each labware uri + location pair. +// Assumes the most recent labware offsets are appended to the end of the list. +function mostRecentUniqueLabwareOffsets( + offsets: LabwareOffset[] | undefined +): LabwareOffset[] | undefined { + return offsets?.filter((offset, index, array) => { + return ( + array.findLastIndex( + firstOffset => + isEqual(firstOffset.locationSequence, offset.locationSequence) && + isEqual(firstOffset.definitionUri, offset.definitionUri) + ) === index + ) + }) +}