From 926b5cd676f79fa57e1256f3ae4af7af1c7fda80 Mon Sep 17 00:00:00 2001 From: koji Date: Thu, 6 Feb 2025 10:28:35 -0500 Subject: [PATCH 1/6] chore: update ubuntu version in workflows of frontend (#17445) * chore: update ubuntu version in workflows of frontend --- .github/workflows/app-test-build-deploy.yaml | 4 ++-- .github/workflows/components-test-build-deploy.yaml | 6 +++--- .github/workflows/js-check.yaml | 2 +- .github/workflows/ll-test-build-deploy.yaml | 8 ++++---- .github/workflows/opentrons-ai-client-test.yaml | 2 +- .github/workflows/react-api-client-test.yaml | 2 +- .github/workflows/step-generation-test.yaml | 2 +- .github/workflows/usb-bridge-lint-test.yaml | 4 ++-- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/app-test-build-deploy.yaml b/.github/workflows/app-test-build-deploy.yaml index 873bfe65c07..13a2051c767 100644 --- a/.github/workflows/app-test-build-deploy.yaml +++ b/.github/workflows/app-test-build-deploy.yaml @@ -52,7 +52,7 @@ env: jobs: js-unit-test: # unit tests for the app's view layer (not the node layer) - runs-on: 'ubuntu-22.04' + runs-on: 'ubuntu-24.04' name: 'opentrons app frontend unit tests' timeout-minutes: 60 steps: @@ -386,7 +386,7 @@ jobs: deploy-release-app: name: 'Deploy built app artifacts to S3' - runs-on: 'ubuntu-22.04' + runs-on: 'ubuntu-24.04' needs: ['js-unit-test', 'backend-unit-test', 'build-app', 'determine-build-type'] if: contains(fromJSON(needs.determine-build-type.outputs.variants), 'release') || contains(fromJSON(needs.determine-build-type.outputs.variants), 'internal-release') diff --git a/.github/workflows/components-test-build-deploy.yaml b/.github/workflows/components-test-build-deploy.yaml index 2b10617c283..aa9bfb7d342 100644 --- a/.github/workflows/components-test-build-deploy.yaml +++ b/.github/workflows/components-test-build-deploy.yaml @@ -41,7 +41,7 @@ jobs: js-unit-test: name: 'components unit tests' timeout-minutes: 30 - runs-on: 'ubuntu-22.04' + runs-on: 'ubuntu-24.04' steps: - uses: 'actions/checkout@v4' - uses: 'actions/setup-node@v4' @@ -75,7 +75,7 @@ jobs: build-components-storybook: name: 'build components artifact' - runs-on: 'ubuntu-22.04' + runs-on: 'ubuntu-24.04' if: github.event_name != 'pull_request' needs: ['js-unit-test'] steps: @@ -135,7 +135,7 @@ jobs: deploy-components: name: 'deploy components storybook artifact to S3' - runs-on: 'ubuntu-22.04' + runs-on: 'ubuntu-24.04' needs: ['js-unit-test', 'build-components-storybook', 'determine-build-type'] if: needs.determine-build-type.outputs.type != 'none' diff --git a/.github/workflows/js-check.yaml b/.github/workflows/js-check.yaml index 807d4a2570c..fa117227fdc 100644 --- a/.github/workflows/js-check.yaml +++ b/.github/workflows/js-check.yaml @@ -39,7 +39,7 @@ env: jobs: checks: name: 'js checks' - runs-on: 'ubuntu-22.04' + runs-on: 'ubuntu-24.04' timeout-minutes: 20 steps: - uses: 'actions/checkout@v4' diff --git a/.github/workflows/ll-test-build-deploy.yaml b/.github/workflows/ll-test-build-deploy.yaml index 35cbc96eced..8027399ffbd 100644 --- a/.github/workflows/ll-test-build-deploy.yaml +++ b/.github/workflows/ll-test-build-deploy.yaml @@ -40,7 +40,7 @@ jobs: js-unit-test: name: 'labware library unit tests' timeout-minutes: 20 - runs-on: 'ubuntu-22.04' + runs-on: 'ubuntu-24.04' steps: - uses: 'actions/checkout@v4' - uses: 'actions/setup-node@v4' @@ -83,7 +83,7 @@ jobs: name: 'labware library e2e tests' needs: ['js-unit-test'] timeout-minutes: 30 - runs-on: 'ubuntu-22.04' + runs-on: 'ubuntu-24.04' steps: - uses: 'actions/checkout@v4' # https://github.com/actions/checkout/issues/290 @@ -123,7 +123,7 @@ jobs: name: 'build labware library artifact' needs: ['js-unit-test'] timeout-minutes: 30 - runs-on: 'ubuntu-22.04' + runs-on: 'ubuntu-24.04' if: github.event_name != 'pull_request' steps: - uses: 'actions/checkout@v4' @@ -170,7 +170,7 @@ jobs: path: labware-library/dist deploy-ll: name: 'deploy LL artifact to S3' - runs-on: 'ubuntu-22.04' + runs-on: 'ubuntu-24.04' needs: ['js-unit-test', 'e2e-test', 'build-ll'] if: github.event_name != 'pull_request' steps: diff --git a/.github/workflows/opentrons-ai-client-test.yaml b/.github/workflows/opentrons-ai-client-test.yaml index 0a78cf73da3..9187667d504 100644 --- a/.github/workflows/opentrons-ai-client-test.yaml +++ b/.github/workflows/opentrons-ai-client-test.yaml @@ -35,7 +35,7 @@ env: jobs: js-unit-test: - runs-on: 'ubuntu-22.04' + runs-on: 'ubuntu-24.04' name: 'opentrons ai frontend unit tests' timeout-minutes: 60 steps: diff --git a/.github/workflows/react-api-client-test.yaml b/.github/workflows/react-api-client-test.yaml index 8a8759f12e1..c9bb0fcc6dd 100644 --- a/.github/workflows/react-api-client-test.yaml +++ b/.github/workflows/react-api-client-test.yaml @@ -34,7 +34,7 @@ jobs: js-unit-test: name: 'api-client and react-api-client unit tests' timeout-minutes: 30 - runs-on: 'ubuntu-22.04' + runs-on: 'ubuntu-24.04' steps: - uses: 'actions/checkout@v4' - uses: 'actions/setup-node@v4' diff --git a/.github/workflows/step-generation-test.yaml b/.github/workflows/step-generation-test.yaml index ac435cf999d..4dd540eccb8 100644 --- a/.github/workflows/step-generation-test.yaml +++ b/.github/workflows/step-generation-test.yaml @@ -32,7 +32,7 @@ env: jobs: js-unit-test: name: 'step generation unit tests' - runs-on: 'ubuntu-22.04' + runs-on: 'ubuntu-24.04' timeout-minutes: 30 steps: - uses: 'actions/checkout@v4' diff --git a/.github/workflows/usb-bridge-lint-test.yaml b/.github/workflows/usb-bridge-lint-test.yaml index bfe11aed61b..7957e898d3c 100644 --- a/.github/workflows/usb-bridge-lint-test.yaml +++ b/.github/workflows/usb-bridge-lint-test.yaml @@ -39,7 +39,7 @@ jobs: lint: name: 'usb-bridge linting' timeout-minutes: 10 - runs-on: 'ubuntu-22.04' + runs-on: 'ubuntu-24.04' steps: - uses: 'actions/checkout@v4' with: @@ -60,7 +60,7 @@ jobs: name: 'usb-bridge package tests' timeout-minutes: 10 needs: [lint] - runs-on: 'ubuntu-22.04' + runs-on: 'ubuntu-24.04' steps: - uses: 'actions/checkout@v4' with: From a693b6c4ad127be2f8bd0158923ed6400782e8dc Mon Sep 17 00:00:00 2001 From: Andy Sigler Date: Thu, 6 Feb 2025 10:30:08 -0500 Subject: [PATCH 2/6] fix(api): correctionVolume can be negative (#17413) --- .../protocol_engine/commands/pipetting_common.py | 2 -- shared-data/command/schemas/12.json | 8 -------- 2 files changed, 10 deletions(-) diff --git a/api/src/opentrons/protocol_engine/commands/pipetting_common.py b/api/src/opentrons/protocol_engine/commands/pipetting_common.py index c0bca3c428a..d240b1fafea 100644 --- a/api/src/opentrons/protocol_engine/commands/pipetting_common.py +++ b/api/src/opentrons/protocol_engine/commands/pipetting_common.py @@ -48,7 +48,6 @@ class AspirateVolumeMixin(BaseModel): correctionVolume: Optional[float] = Field( None, description="The correction volume in uL.", - ge=0, ) @@ -65,7 +64,6 @@ class DispenseVolumeMixin(BaseModel): correctionVolume: Optional[float] = Field( None, description="The correction volume in uL.", - ge=0, ) diff --git a/shared-data/command/schemas/12.json b/shared-data/command/schemas/12.json index 2e40c357c7d..08c86764882 100644 --- a/shared-data/command/schemas/12.json +++ b/shared-data/command/schemas/12.json @@ -67,7 +67,6 @@ "correctionVolume": { "anyOf": [ { - "minimum": 0.0, "type": "number" }, { @@ -176,7 +175,6 @@ "correctionVolume": { "anyOf": [ { - "minimum": 0.0, "type": "number" }, { @@ -215,7 +213,6 @@ "correctionVolume": { "anyOf": [ { - "minimum": 0.0, "type": "number" }, { @@ -413,7 +410,6 @@ "correctionVolume": { "anyOf": [ { - "minimum": 0.0, "type": "number" }, { @@ -1521,7 +1517,6 @@ "correctionVolume": { "anyOf": [ { - "minimum": 0.0, "type": "number" }, { @@ -1565,7 +1560,6 @@ "correctionVolume": { "anyOf": [ { - "minimum": 0.0, "type": "number" }, { @@ -1651,7 +1645,6 @@ "correctionVolume": { "anyOf": [ { - "minimum": 0.0, "type": "number" }, { @@ -1914,7 +1907,6 @@ "correctionVolume": { "anyOf": [ { - "minimum": 0.0, "type": "number" }, { From aae2bfab3b783e34f7ae717d1e189b3de94c6720 Mon Sep 17 00:00:00 2001 From: Nick Diehl <47604184+ncdiehl11@users.noreply.github.com> Date: Thu, 6 Feb 2025 10:56:08 -0500 Subject: [PATCH 3/6] fix(protocol-designer, step-generation): update prewet behavior in PD (#17433) This PR adds prewet to distribute, and updates the logic for when to use prewet for all compound liquid handling command creators. The prewet volume should always be set to the volume of the aspiration that is about to be called (including disposal volume for `distribute`). Prewet commands should be emmitted anytime a new tip is used for an aspiration. Also, I update the UI for the prewet form field in MoveLiquidTools Closes AUTH-908 --- .../src/assets/localization/en/tooltip.json | 2 +- .../molecules/ToggleStepFormField/index.tsx | 36 ++-- .../StepForm/PipetteFields/LabwareField.tsx | 1 + .../StepForm/StepFormToolbox.tsx | 1 + .../StepTools/MoveLiquidTools/index.tsx | 12 +- .../src/__tests__/distribute.test.ts | 198 ++++++++++++++++++ .../commandCreators/compound/distribute.ts | 27 +++ .../src/commandCreators/compound/transfer.ts | 2 +- .../src/fixtures/commandFixtures.ts | 23 +- 9 files changed, 277 insertions(+), 25 deletions(-) diff --git a/protocol-designer/src/assets/localization/en/tooltip.json b/protocol-designer/src/assets/localization/en/tooltip.json index 849844a0c34..1445cedf0bd 100644 --- a/protocol-designer/src/assets/localization/en/tooltip.json +++ b/protocol-designer/src/assets/localization/en/tooltip.json @@ -65,7 +65,7 @@ "newLocation": "New location to move the selected labware", "nozzles": "Partial pickup requires a tip rack directly on the deck. Full rack pickup requires the Flex 96 Tip Rack Adapter.", "pipette": "Select the pipette you want to use", - "preWetTip": "Pre-wet by aspirating and dispensing 2/3 of the tip’s max volume", + "preWetTip": "Pre-wet by aspirating and dispensing the total aspiration volume", "setTemperature": "Select the temperature to set your module to", "wells": "Select wells", "volume": "Volume to dispense in each well" diff --git a/protocol-designer/src/molecules/ToggleStepFormField/index.tsx b/protocol-designer/src/molecules/ToggleStepFormField/index.tsx index ad609acf0b9..3068f4e0e69 100644 --- a/protocol-designer/src/molecules/ToggleStepFormField/index.tsx +++ b/protocol-designer/src/molecules/ToggleStepFormField/index.tsx @@ -1,5 +1,6 @@ import { ALIGN_CENTER, + Check, COLORS, DIRECTION_COLUMN, Flex, @@ -16,12 +17,13 @@ import { ToggleButton } from '../../atoms/ToggleButton' interface ToggleStepFormFieldProps { title: string isSelected: boolean - onLabel: string - offLabel: string toggleUpdateValue: (value: unknown) => void toggleValue: unknown tooltipContent: string | null - isDisabled: boolean + isDisabled?: boolean + onLabel?: string + offLabel?: string + toggleElement?: 'toggle' | 'checkbox' } export function ToggleStepFormField( props: ToggleStepFormFieldProps @@ -34,7 +36,8 @@ export function ToggleStepFormField( toggleUpdateValue, toggleValue, tooltipContent, - isDisabled, + isDisabled = false, + toggleElement = 'toggle', } = props const [targetProps, tooltipProps] = useHoverTooltip() @@ -42,7 +45,7 @@ export function ToggleStepFormField( <> { if (!isDisabled) { toggleUpdateValue(!toggleValue) @@ -50,11 +53,7 @@ export function ToggleStepFormField( }} disabled={isDisabled} > - {tooltipContent != null ? ( - {tooltipContent} - ) : null} - - + {title} @@ -72,15 +72,21 @@ export function ToggleStepFormField( > {isSelected ? onLabel : offLabel} - + {toggleElement === 'toggle' ? ( + + ) : ( + + )} + {tooltipContent != null ? ( + {tooltipContent} + ) : null} ) } diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/PipetteFields/LabwareField.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/PipetteFields/LabwareField.tsx index 07e08b8a299..00f68dcd46c 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/PipetteFields/LabwareField.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/PipetteFields/LabwareField.tsx @@ -31,6 +31,7 @@ export function LabwareField(props: FieldProps): JSX.Element { onExit={() => { dispatch(hoverSelection({ id: null, text: null })) }} + width="100%" /> ) } diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepFormToolbox.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepFormToolbox.tsx index 253889423ca..45eee9219f8 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepFormToolbox.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepFormToolbox.tsx @@ -360,6 +360,7 @@ export function StepFormToolbox(props: StepFormToolboxProps): JSX.Element { } + width="21.875rem" >
{tab === 'aspirate' ? ( - ) : null} = ( ] : [] const aspirateDisposalVolumeOnce = chunkIndex === 0 ? disposalVolume : 0 + // if changeTip is 'once' or 'never', only prewet for first aspiration + // if changeTip is 'always', pre-wet for each chunk + const preWetTipCommands = + args.preWetTip && + (args.changeTip === 'always' ? true : chunkIndex === 0) + ? mixUtil({ + pipette: args.pipette, + labware: args.sourceLabware, + well: args.sourceWell, + volume: + args.volume * destWellChunk.length + + (args.changeTip === 'always' + ? disposalVolume + : aspirateDisposalVolumeOnce), + times: 1, + offsetFromBottomMm: aspirateOffsetFromBottomMm, + aspirateFlowRateUlSec, + dispenseFlowRateUlSec, + aspirateDelaySeconds: aspirateDelay?.seconds, + dispenseDelaySeconds: dispenseDelay?.seconds, + tipRack: args.tipRack, + xOffset: aspirateXOffset, + yOffset: aspirateYOffset, + nozzles, + }) + : [] return [ ...tipCommands, + ...preWetTipCommands, ...configureForVolumeCommand, ...mixBeforeAspirateCommands, curryCommandCreator(aspirate, { diff --git a/step-generation/src/commandCreators/compound/transfer.ts b/step-generation/src/commandCreators/compound/transfer.ts index c4b3d183ea2..282b92ad3e1 100644 --- a/step-generation/src/commandCreators/compound/transfer.ts +++ b/step-generation/src/commandCreators/compound/transfer.ts @@ -286,7 +286,7 @@ export const transfer: CommandCreator = ( pipette: args.pipette, labware: args.sourceLabware, well: sourceWell, - volume: Math.max(subTransferVol), + volume: subTransferVol, times: 1, offsetFromBottomMm: aspirateOffsetFromBottomMm, aspirateFlowRateUlSec, diff --git a/step-generation/src/fixtures/commandFixtures.ts b/step-generation/src/fixtures/commandFixtures.ts index 37e4ed07a44..056ad5114cd 100644 --- a/step-generation/src/fixtures/commandFixtures.ts +++ b/step-generation/src/fixtures/commandFixtures.ts @@ -138,7 +138,12 @@ export const makeAspirateHelper: MakeAspDispHelper = bakedP ...params, }, }) -export const makeMoveToWellHelper = (wellName: string, labwareId?: string) => ({ +export const makeMoveToWellHelper = ( + wellName: string, + labwareId?: string, + forceDirect?: boolean, + minimumZHeight?: number +) => ({ commandType: 'moveToWell', key: expect.any(String), params: { @@ -153,6 +158,8 @@ export const makeMoveToWellHelper = (wellName: string, labwareId?: string) => ({ z: 11.54, }, }, + forceDirect, + minimumZHeight, }, }) export const makeAirGapHelper = (volume: number) => ({ @@ -268,18 +275,25 @@ export const makeTouchTipHelper: MakeTouchTipHelper = bakedParams => ( key: expect.any(String), params: { ..._defaultTouchTipParams, ...bakedParams, wellName, ...params }, }) -export const delayCommand = (seconds: number): CreateCommand => ({ +export const delayCommand = ( + seconds: number, + message?: string +): CreateCommand => ({ commandType: 'waitForDuration', key: expect.any(String), params: { seconds: seconds, + message, }, }) export const delayWithOffset = ( wellName: string, labwareId: string, seconds?: number, - zOffset?: number + zOffset?: number, + forceDirect?: boolean, + minimumZHeight?: number, + message?: string ): CreateCommand[] => [ { commandType: 'moveToWell', @@ -296,6 +310,8 @@ export const delayWithOffset = ( z: zOffset || 14, }, }, + forceDirect, + minimumZHeight, }, }, { @@ -303,6 +319,7 @@ export const delayWithOffset = ( key: expect.any(String), params: { seconds: seconds ?? 12, + message, }, }, ] From ba079631618172694a33d31b29459732cb7c73ed Mon Sep 17 00:00:00 2001 From: Max Marrone Date: Thu, 6 Feb 2025 11:10:46 -0500 Subject: [PATCH 4/6] feat(robot-server): Allow adding multiple labware offsets in a single request (#17436) --- api-client/src/runs/createLabwareOffset.ts | 24 +++-- .../runs/useCreateLabwareOffsetMutation.ts | 33 +++---- .../robot_server/labware_offsets/router.py | 76 +++++++++++----- .../maintenance_runs/router/labware_router.py | 38 ++++++-- .../runs/router/labware_router.py | 42 +++++++-- .../robot_server/service/dependencies.py | 22 ++++- .../http_api/test_labware_offsets.tavern.yaml | 90 +++++++++++++++++++ .../router/test_labware_router.py | 52 +++++++++-- .../tests/runs/router/test_labware_router.py | 50 +++++++++-- 9 files changed, 345 insertions(+), 82 deletions(-) diff --git a/api-client/src/runs/createLabwareOffset.ts b/api-client/src/runs/createLabwareOffset.ts index 0b91566cf46..e1c125f8254 100644 --- a/api-client/src/runs/createLabwareOffset.ts +++ b/api-client/src/runs/createLabwareOffset.ts @@ -2,17 +2,25 @@ import { POST, request } from '../request' import type { ResponsePromise } from '../request' import type { HostConfig } from '../types' -import type { LegacyLabwareOffsetCreateData, Run } from './types' +import type { LabwareOffset, LegacyLabwareOffsetCreateData } from './types' export function createLabwareOffset( config: HostConfig, runId: string, data: LegacyLabwareOffsetCreateData -): ResponsePromise { - return request( - POST, - `/runs/${runId}/labware_offsets`, - { data }, - config - ) +): ResponsePromise +export function createLabwareOffset( + config: HostConfig, + runId: string, + data: LegacyLabwareOffsetCreateData[] +): ResponsePromise +export function createLabwareOffset( + config: HostConfig, + runId: string, + data: LegacyLabwareOffsetCreateData | LegacyLabwareOffsetCreateData[] +): ResponsePromise { + return request< + LabwareOffset | LabwareOffset[], + { data: LegacyLabwareOffsetCreateData | LegacyLabwareOffsetCreateData[] } + >(POST, `/runs/${runId}/labware_offsets`, { data }, config) } diff --git a/react-api-client/src/runs/useCreateLabwareOffsetMutation.ts b/react-api-client/src/runs/useCreateLabwareOffsetMutation.ts index f1b04505b30..28628da1555 100644 --- a/react-api-client/src/runs/useCreateLabwareOffsetMutation.ts +++ b/react-api-client/src/runs/useCreateLabwareOffsetMutation.ts @@ -3,8 +3,8 @@ import { createLabwareOffset } from '@opentrons/api-client' import { useHost } from '../api' import type { HostConfig, - Run, LegacyLabwareOffsetCreateData, + LabwareOffset, } from '@opentrons/api-client' import type { UseMutationResult, UseMutateAsyncFunction } from 'react-query' @@ -14,12 +14,12 @@ interface CreateLabwareOffsetParams { } export type UseCreateLabwareOffsetMutationResult = UseMutationResult< - Run, + LabwareOffset, unknown, CreateLabwareOffsetParams > & { createLabwareOffset: UseMutateAsyncFunction< - Run, + LabwareOffset, unknown, CreateLabwareOffsetParams > @@ -29,19 +29,22 @@ export function useCreateLabwareOffsetMutation(): UseCreateLabwareOffsetMutation const host = useHost() const queryClient = useQueryClient() - const mutation = useMutation( - ({ runId, data }) => - createLabwareOffset(host as HostConfig, runId, data) - .then(response => { - queryClient.invalidateQueries([host, 'runs']).catch((e: Error) => { - console.error(`error invalidating runs query: ${e.message}`) - }) - return response.data - }) - .catch((e: Error) => { - console.error(`error creating labware offsets: ${e.message}`) - throw e + const mutation = useMutation< + LabwareOffset, + unknown, + CreateLabwareOffsetParams + >(({ runId, data }) => + createLabwareOffset(host as HostConfig, runId, data) + .then(response => { + queryClient.invalidateQueries([host, 'runs']).catch((e: Error) => { + console.error(`error invalidating runs query: ${e.message}`) }) + return response.data + }) + .catch((e: Error) => { + console.error(`error creating labware offsets: ${e.message}`) + throw e + }) ) return { diff --git a/robot-server/robot_server/labware_offsets/router.py b/robot-server/robot_server/labware_offsets/router.py index 4ebf532d657..7d00fbc68d6 100644 --- a/robot-server/robot_server/labware_offsets/router.py +++ b/robot-server/robot_server/labware_offsets/router.py @@ -12,7 +12,10 @@ from opentrons.protocol_engine import ModuleModel from robot_server.labware_offsets.models import LabwareOffsetNotFound -from robot_server.service.dependencies import get_current_time, get_unique_id +from robot_server.service.dependencies import ( + UniqueIDFactory, + get_current_time, +) from robot_server.service.json_api.request import RequestModel from robot_server.service.json_api.response import ( MultiBodyMeta, @@ -42,42 +45,67 @@ @PydanticResponse.wrap_route( router.post, path="/labwareOffsets", - summary="Store a labware offset", + summary="Store labware offsets", description=textwrap.dedent( """\ - Store a labware offset for later retrieval through `GET /labwareOffsets`. + Store labware offsets for later retrieval through `GET /labwareOffsets`. On its own, this does not affect robot motion. - To do that, you must add the offset to a run, through the `/runs` endpoints. + To do that, you must add the offsets to a run, through the `/runs` endpoints. + + The response body's `data` will either be a single offset or a list of offsets, + depending on whether you provided a single offset or a list in the request body's `data`. """ ), status_code=201, include_in_schema=False, # todo(mm, 2025-01-08): Include for v8.4.0. ) -async def post_labware_offset( # noqa: D103 +async def post_labware_offsets( # noqa: D103 store: Annotated[LabwareOffsetStore, fastapi.Depends(get_labware_offset_store)], - new_offset_id: Annotated[str, fastapi.Depends(get_unique_id)], + new_offset_id_factory: Annotated[UniqueIDFactory, fastapi.Depends(UniqueIDFactory)], new_offset_created_at: Annotated[datetime, fastapi.Depends(get_current_time)], - request_body: Annotated[RequestModel[StoredLabwareOffsetCreate], fastapi.Body()], -) -> PydanticResponse[SimpleBody[StoredLabwareOffset]]: - new_offset = IncomingStoredLabwareOffset( - id=new_offset_id, - createdAt=new_offset_created_at, - definitionUri=request_body.data.definitionUri, - locationSequence=request_body.data.locationSequence, - vector=request_body.data.vector, + request_body: Annotated[ + RequestModel[StoredLabwareOffsetCreate | list[StoredLabwareOffsetCreate]], + fastapi.Body(), + ], +) -> PydanticResponse[SimpleBody[StoredLabwareOffset | list[StoredLabwareOffset]]]: + new_offsets = [ + IncomingStoredLabwareOffset( + id=new_offset_id_factory.get(), + createdAt=new_offset_created_at, + definitionUri=request_body_element.definitionUri, + locationSequence=request_body_element.locationSequence, + vector=request_body_element.vector, + ) + for request_body_element in ( + request_body.data + if isinstance(request_body.data, list) + else [request_body.data] + ) + ] + + for new_offset in new_offsets: + store.add(new_offset) + + stored_offsets = [ + StoredLabwareOffset.model_construct( + id=incoming.id, + createdAt=incoming.createdAt, + definitionUri=incoming.definitionUri, + locationSequence=incoming.locationSequence, + vector=incoming.vector, + ) + for incoming in new_offsets + ] + + # Return a list if the client POSTed a list, or an object if the client POSTed an object. + # For some reason, mypy needs to be given the type annotation explicitly. + response_data: StoredLabwareOffset | list[StoredLabwareOffset] = ( + stored_offsets if isinstance(request_body.data, list) else stored_offsets[0] ) - store.add(new_offset) + return await PydanticResponse.create( - content=SimpleBody.model_construct( - data=StoredLabwareOffset( - id=new_offset_id, - createdAt=new_offset_created_at, - definitionUri=request_body.data.definitionUri, - locationSequence=request_body.data.locationSequence, - vector=request_body.data.vector, - ) - ), + content=SimpleBody.model_construct(data=response_data), status_code=201, ) diff --git a/robot-server/robot_server/maintenance_runs/router/labware_router.py b/robot-server/robot_server/maintenance_runs/router/labware_router.py index c64e8b7db97..1271710de1e 100644 --- a/robot-server/robot_server/maintenance_runs/router/labware_router.py +++ b/robot-server/robot_server/maintenance_runs/router/labware_router.py @@ -36,33 +36,57 @@ "There is no matching `GET /maintenance_runs/{runId}/labware_offsets` endpoint." " To read the list of labware offsets currently on the run," " see the run's `labwareOffsets` field." + "\n\n" + "The response body's `data` will either be a single offset or a list of offsets," + " depending on whether you provided a single offset or a list in the request body's `data`." ), status_code=status.HTTP_201_CREATED, responses={ - status.HTTP_201_CREATED: {"model": SimpleBody[LabwareOffset]}, + status.HTTP_201_CREATED: { + "model": SimpleBody[LabwareOffset | list[LabwareOffset]] + }, status.HTTP_404_NOT_FOUND: {"model": ErrorBody[RunNotFound]}, status.HTTP_409_CONFLICT: {"model": ErrorBody[RunNotIdle]}, }, ) async def add_labware_offset( - request_body: RequestModel[LabwareOffsetCreate | LegacyLabwareOffsetCreate], + request_body: RequestModel[ + LabwareOffsetCreate + | LegacyLabwareOffsetCreate + | list[LabwareOffsetCreate | LegacyLabwareOffsetCreate] + ], run_orchestrator_store: Annotated[ MaintenanceRunOrchestratorStore, Depends(get_maintenance_run_orchestrator_store) ], run: Annotated[MaintenanceRun, Depends(get_run_data_from_url)], -) -> PydanticResponse[SimpleBody[LabwareOffset]]: - """Add a labware offset to a maintenance run. +) -> PydanticResponse[SimpleBody[LabwareOffset | list[LabwareOffset]]]: + """Add labware offsets to a maintenance run. Args: request_body: New labware offset request data from request body. run_orchestrator_store: Engine storage interface. run: Run response data by ID from URL; ensures 404 if run not found. """ - added_offset = run_orchestrator_store.add_labware_offset(request_body.data) - log.info(f'Added labware offset "{added_offset.id}"' f' to run "{run.id}".') + offsets_to_add = ( + request_body.data + if isinstance(request_body.data, list) + else [request_body.data] + ) + + added_offsets: list[LabwareOffset] = [] + for offset_to_add in offsets_to_add: + added_offset = run_orchestrator_store.add_labware_offset(offset_to_add) + added_offsets.append(added_offset) + log.info(f'Added labware offset "{added_offset.id}" to run "{run.id}".') + + # Return a list if the client POSTed a list, or an object if the client POSTed an object. + # For some reason, mypy needs to be given the type annotation explicitly. + response_data: LabwareOffset | list[LabwareOffset] = ( + added_offsets if isinstance(request_body.data, list) else added_offsets[0] + ) return await PydanticResponse.create( - content=SimpleBody.model_construct(data=added_offset), + content=SimpleBody.model_construct(data=response_data), status_code=status.HTTP_201_CREATED, ) diff --git a/robot-server/robot_server/runs/router/labware_router.py b/robot-server/robot_server/runs/router/labware_router.py index 78c880a2df5..970af7415c3 100644 --- a/robot-server/robot_server/runs/router/labware_router.py +++ b/robot-server/robot_server/runs/router/labware_router.py @@ -35,29 +35,38 @@ @PydanticResponse.wrap_route( labware_router.post, path="/runs/{runId}/labware_offsets", - summary="Add a labware offset to a run", + summary="Add labware offsets to a run", description=( - "Add a labware offset to an existing run, returning the created offset." + "Add labware offsets to an existing run, returning the created offsets." "\n\n" "There is no matching `GET /runs/{runId}/labware_offsets` endpoint." " To read the list of labware offsets currently on the run," " see the run's `labwareOffsets` field." + "\n\n" + "The response body's `data` will either be a single offset or a list of offsets," + " depending on whether you provided a single offset or a list in the request body's `data`." ), status_code=status.HTTP_201_CREATED, responses={ - status.HTTP_201_CREATED: {"model": SimpleBody[LabwareOffset]}, + status.HTTP_201_CREATED: { + "model": SimpleBody[LabwareOffset | list[LabwareOffset]] + }, status.HTTP_404_NOT_FOUND: {"model": ErrorBody[RunNotFound]}, status.HTTP_409_CONFLICT: {"model": ErrorBody[Union[RunStopped, RunNotIdle]]}, }, ) async def add_labware_offset( - request_body: RequestModel[LegacyLabwareOffsetCreate | LabwareOffsetCreate], + request_body: RequestModel[ + LegacyLabwareOffsetCreate + | LabwareOffsetCreate + | list[LegacyLabwareOffsetCreate | LabwareOffsetCreate] + ], run_orchestrator_store: Annotated[ RunOrchestratorStore, Depends(get_run_orchestrator_store) ], run: Annotated[Run, Depends(get_run_data_from_url)], -) -> PydanticResponse[SimpleBody[LabwareOffset]]: - """Add a labware offset to a run. +) -> PydanticResponse[SimpleBody[LabwareOffset | list[LabwareOffset]]]: + """Add labware offsets to a run. Args: request_body: New labware offset request data from request body. @@ -69,11 +78,26 @@ async def add_labware_offset( status.HTTP_409_CONFLICT ) - added_offset = run_orchestrator_store.add_labware_offset(request_body.data) - log.info(f'Added labware offset "{added_offset.id}"' f' to run "{run.id}".') + offsets_to_add = ( + request_body.data + if isinstance(request_body.data, list) + else [request_body.data] + ) + + added_offsets: list[LabwareOffset] = [] + for offset_to_add in offsets_to_add: + added_offset = run_orchestrator_store.add_labware_offset(offset_to_add) + added_offsets.append(added_offset) + log.info(f'Added labware offset "{added_offset.id}" to run "{run.id}".') + + # Return a list if the client POSTed a list, or an object if the client POSTed an object. + # For some reason, mypy needs to be given the type annotation explicitly. + response_data: LabwareOffset | list[LabwareOffset] = ( + added_offsets if isinstance(request_body.data, list) else added_offsets[0] + ) return await PydanticResponse.create( - content=SimpleBody.model_construct(data=added_offset), + content=SimpleBody.model_construct(data=response_data), status_code=status.HTTP_201_CREATED, ) diff --git a/robot-server/robot_server/service/dependencies.py b/robot-server/robot_server/service/dependencies.py index b27e014a3e9..80d811616c9 100644 --- a/robot-server/robot_server/service/dependencies.py +++ b/robot-server/robot_server/service/dependencies.py @@ -35,7 +35,27 @@ async def get_session_manager( async def get_unique_id() -> str: """Get a unique ID string to use as a resource identifier.""" - return str(uuid4()) + return UniqueIDFactory().get() + + +class UniqueIDFactory: + """ + This is equivalent to the `get_unique_id()` free function. Wrapping it in a factory + class makes things easier for FastAPI endpoint functions that need multiple unique + IDs. They can do: + + unique_id_factory: UniqueIDFactory = fastapi.Depends(UniqueIDFactory) + + And then: + + unique_id_1 = await unique_id_factory.get() + unique_id_2 = await unique_id_factory.get() + """ + + @staticmethod + def get() -> str: + """Get a unique ID to use as a resource identifier.""" + return str(uuid4()) async def get_current_time() -> datetime: diff --git a/robot-server/tests/integration/http_api/test_labware_offsets.tavern.yaml b/robot-server/tests/integration/http_api/test_labware_offsets.tavern.yaml index 0745f10a2ae..754a20df132 100644 --- a/robot-server/tests/integration/http_api/test_labware_offsets.tavern.yaml +++ b/robot-server/tests/integration/http_api/test_labware_offsets.tavern.yaml @@ -150,14 +150,98 @@ stages: cursor: 0 totalLength: 0 +--- +test_name: Test POSTing multiple offsets in a single request + +marks: + - usefixtures: + - ot3_server_base_url + +stages: + - name: POST multiple offsets and check the response + request: + method: POST + url: '{ot3_server_base_url}/labwareOffsets' + json: + data: + - definitionUri: testNamespace/loadName1/1 + locationSequence: + - kind: onAddressableArea + addressableAreaName: A1 + vector: + x: 1 + y: 1 + z: 1 + - definitionUri: testNamespace/loadName2/1 + locationSequence: + - kind: onAddressableArea + addressableAreaName: A2 + vector: + x: 2 + y: 2 + z: 2 + response: + status_code: 201 + json: + data: + - id: !anystr + createdAt: !anystr + definitionUri: testNamespace/loadName1/1 + locationSequence: + - kind: onAddressableArea + addressableAreaName: A1 + vector: + x: 1 + y: 1 + z: 1 + - id: !anystr + createdAt: !anystr + definitionUri: testNamespace/loadName2/1 + locationSequence: + - kind: onAddressableArea + addressableAreaName: A2 + vector: + x: 2 + y: 2 + z: 2 + save: + json: + offset_1_data: data[0] + offset_2_data: data[1] + + - name: POST an empty list of offsets and check the response + request: + method: POST + url: '{ot3_server_base_url}/labwareOffsets' + json: + data: [] + response: + status_code: 201 + json: + data: [] + + - name: Make sure all offsets got stored + request: + url: '{ot3_server_base_url}/labwareOffsets' + response: + json: + data: + - !force_format_include '{offset_1_data}' + - !force_format_include '{offset_2_data}' + meta: + cursor: 0 + totalLength: 2 + --- # Some of the filter query parameters can have `null` values or be omitted, # with different semantics between the two. That distinction takes a bit of care to # preserve across our code, so here we test it specifically. test_name: Test null vs. omitted filter query parameters + marks: - usefixtures: - ot3_server_base_url + stages: - name: POST test offset 1 request: @@ -180,6 +264,7 @@ stages: save: json: offset_1_data: data + - name: POST test offset 2 request: method: POST @@ -202,6 +287,7 @@ stages: save: json: offset_2_data: data + - name: POST test offset 3 request: method: POST @@ -224,6 +310,7 @@ stages: save: json: offset_3_data: data + - name: POST test offset 4 request: method: POST @@ -247,6 +334,7 @@ stages: save: json: offset_4_data: data + - name: Test no filters request: url: '{ot3_server_base_url}/labwareOffsets' @@ -258,6 +346,7 @@ stages: - !force_format_include '{offset_3_data}' - !force_format_include '{offset_4_data}' meta: !anydict + - name: Test filtering on locationModuleModel=null request: url: '{ot3_server_base_url}/labwareOffsets?locationModuleModel=null' @@ -267,6 +356,7 @@ stages: - !force_format_include '{offset_1_data}' - !force_format_include '{offset_3_data}' meta: !anydict + - name: Test filtering on locationDefinitionUri=null request: url: '{ot3_server_base_url}/labwareOffsets?locationDefinitionUri=null' diff --git a/robot-server/tests/maintenance_runs/router/test_labware_router.py b/robot-server/tests/maintenance_runs/router/test_labware_router.py index 72b1e95e2f7..e1691c1b26b 100644 --- a/robot-server/tests/maintenance_runs/router/test_labware_router.py +++ b/robot-server/tests/maintenance_runs/router/test_labware_router.py @@ -50,20 +50,32 @@ def labware_definition(minimal_labware_def: LabwareDefDict) -> LabwareDefinition return LabwareDefinition.model_validate(minimal_labware_def) -async def test_add_labware_offset( +async def test_add_labware_offsets( decoy: Decoy, mock_maintenance_run_orchestrator_store: MaintenanceRunOrchestratorStore, run: MaintenanceRun, ) -> None: - """It should add the labware offset to the engine, assuming the run is current.""" - labware_offset_request = pe_types.LegacyLabwareOffsetCreate( + """It should add the labware offsets to the engine, assuming the run is current.""" + labware_offset_request_1 = pe_types.LegacyLabwareOffsetCreate( definitionUri="namespace_1/load_name_1/123", location=pe_types.LegacyLabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), vector=pe_types.LabwareOffsetVector(x=1, y=2, z=3), ) + labware_offset_request_2 = pe_types.LegacyLabwareOffsetCreate( + definitionUri="namespace_1/load_name_2/123", + location=pe_types.LegacyLabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), + vector=pe_types.LabwareOffsetVector(x=1, y=2, z=3), + ) - labware_offset = pe_types.LabwareOffset( - id="labware-offset-id", + labware_offset_1 = pe_types.LabwareOffset( + id="labware-offset-id-1", + createdAt=datetime(year=2022, month=2, day=2), + definitionUri="labware-definition-uri", + location=pe_types.LegacyLabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), + vector=pe_types.LabwareOffsetVector(x=0, y=0, z=0), + ) + labware_offset_2 = pe_types.LabwareOffset( + id="labware-offset-id-2", createdAt=datetime(year=2022, month=2, day=2), definitionUri="labware-definition-uri", location=pe_types.LegacyLabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), @@ -72,17 +84,39 @@ async def test_add_labware_offset( decoy.when( mock_maintenance_run_orchestrator_store.add_labware_offset( - labware_offset_request + labware_offset_request_1 + ) + ).then_return(labware_offset_1) + decoy.when( + mock_maintenance_run_orchestrator_store.add_labware_offset( + labware_offset_request_2 ) - ).then_return(labware_offset) + ).then_return(labware_offset_2) result = await add_labware_offset( - request_body=RequestModel(data=labware_offset_request), + request_body=RequestModel(data=labware_offset_request_1), run_orchestrator_store=mock_maintenance_run_orchestrator_store, run=run, ) + assert result.content == SimpleBody(data=labware_offset_1) + assert result.status_code == 201 - assert result.content == SimpleBody(data=labware_offset) + result = await add_labware_offset( + request_body=RequestModel( + data=[labware_offset_request_1, labware_offset_request_2] + ), + run_orchestrator_store=mock_maintenance_run_orchestrator_store, + run=run, + ) + assert result.content == SimpleBody(data=[labware_offset_1, labware_offset_2]) + assert result.status_code == 201 + + result = await add_labware_offset( + request_body=RequestModel(data=[]), + run_orchestrator_store=mock_maintenance_run_orchestrator_store, + run=run, + ) + assert result.content == SimpleBody(data=[]) assert result.status_code == 201 diff --git a/robot-server/tests/runs/router/test_labware_router.py b/robot-server/tests/runs/router/test_labware_router.py index 2b55b4097f6..e8304784fbf 100644 --- a/robot-server/tests/runs/router/test_labware_router.py +++ b/robot-server/tests/runs/router/test_labware_router.py @@ -53,20 +53,32 @@ def labware_definition(minimal_labware_def: LabwareDefDict) -> LabwareDefinition return LabwareDefinition.model_validate(minimal_labware_def) -async def test_add_labware_offset( +async def test_add_labware_offsets( decoy: Decoy, mock_run_orchestrator_store: RunOrchestratorStore, run: Run, ) -> None: - """It should add the labware offset to the engine, assuming the run is current.""" - labware_offset_request = pe_types.LegacyLabwareOffsetCreate( + """It should add the labware offsets to the engine, assuming the run is current.""" + labware_offset_request_1 = pe_types.LegacyLabwareOffsetCreate( definitionUri="namespace_1/load_name_1/123", location=pe_types.LegacyLabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), vector=pe_types.LabwareOffsetVector(x=1, y=2, z=3), ) + labware_offset_request_2 = pe_types.LegacyLabwareOffsetCreate( + definitionUri="namespace_1/load_name_2/123", + location=pe_types.LegacyLabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), + vector=pe_types.LabwareOffsetVector(x=1, y=2, z=3), + ) - labware_offset = pe_types.LabwareOffset( - id="labware-offset-id", + labware_offset_1 = pe_types.LabwareOffset( + id="labware-offset-id-1", + createdAt=datetime(year=2022, month=2, day=2), + definitionUri="labware-definition-uri", + location=pe_types.LegacyLabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), + vector=pe_types.LabwareOffsetVector(x=0, y=0, z=0), + ) + labware_offset_2 = pe_types.LabwareOffset( + id="labware-offset-id-2", createdAt=datetime(year=2022, month=2, day=2), definitionUri="labware-definition-uri", location=pe_types.LegacyLabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), @@ -74,16 +86,36 @@ async def test_add_labware_offset( ) decoy.when( - mock_run_orchestrator_store.add_labware_offset(labware_offset_request) - ).then_return(labware_offset) + mock_run_orchestrator_store.add_labware_offset(labware_offset_request_1) + ).then_return(labware_offset_1) + decoy.when( + mock_run_orchestrator_store.add_labware_offset(labware_offset_request_2) + ).then_return(labware_offset_2) result = await add_labware_offset( - request_body=RequestModel(data=labware_offset_request), + request_body=RequestModel(data=labware_offset_request_1), run_orchestrator_store=mock_run_orchestrator_store, run=run, ) + assert result.content == SimpleBody(data=labware_offset_1) + assert result.status_code == 201 - assert result.content == SimpleBody(data=labware_offset) + result = await add_labware_offset( + request_body=RequestModel( + data=[labware_offset_request_1, labware_offset_request_2] + ), + run_orchestrator_store=mock_run_orchestrator_store, + run=run, + ) + assert result.content == SimpleBody(data=[labware_offset_1, labware_offset_2]) + assert result.status_code == 201 + + result = await add_labware_offset( + request_body=RequestModel(data=[]), + run_orchestrator_store=mock_run_orchestrator_store, + run=run, + ) + assert result.content == SimpleBody(data=[]) assert result.status_code == 201 From ff9f365a06a868c1f08134605290999f7ac9a17b Mon Sep 17 00:00:00 2001 From: emilyburghardt Date: Thu, 6 Feb 2025 10:21:10 -0700 Subject: [PATCH 5/6] API 2.22 docs versioning update (#17400) # Overview Adding API 2.22 to our PAPI versioning page. Now, we document API 2.22 for customers, but it's clear that this version only contains changes for our commercial partners. ## Test Plan and Hands on Testing Needs to be built ## Changelog -update versions table -add a simple version description for API 2.22 ## Review requests ## Risk assessment low --- api/docs/v2/versioning.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/api/docs/v2/versioning.rst b/api/docs/v2/versioning.rst index 935011f61dd..9f615ed04ff 100644 --- a/api/docs/v2/versioning.rst +++ b/api/docs/v2/versioning.rst @@ -84,6 +84,8 @@ This table lists the correspondence between Protocol API versions and robot soft +-------------+------------------------------+ | API Version | Introduced in Robot Software | +=============+==============================+ +| 2.22 | 8.3.0 | ++-------------+------------------------------+ | 2.21 | 8.2.0 | +-------------+------------------------------+ | 2.20 | 8.0.0 | @@ -136,6 +138,10 @@ This table lists the correspondence between Protocol API versions and robot soft Changes in API Versions ======================= +Version 2.22 +------------- +This version includes beta features for our commercial partners. + Version 2.21 ------------ - Adds :py:class:`.AbsorbanceReaderContext` to support the :ref:`Absorbance Plate Reader Module `. Use the load name ``absorbanceReaderV1`` with :py:meth:`.ProtocolContext.load_module` to add an Absorbance Plate Reader to a protocol. From 73b70f44648d8d4369d882c9f4de28565fbe509c Mon Sep 17 00:00:00 2001 From: Josh McVey Date: Thu, 6 Feb 2025 12:20:41 -0600 Subject: [PATCH 6/6] chore(ci): adjust codecov settings and update step-generation workflow (#17424) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Most js projects were not uploading to codecov before the change in #17406 to produce lcov output. - Now that they are, we don't want ❌ everywhere so let us adjust the patch threshold and catch up the ignore list. - Tackle any specific areas of coverage deficit strategically. - In addition convert `.github/workflows/step-generation-test.yaml` to the pattern of PD like in #17406 --- .codecov.yml | 66 +++++++++++++++------ .github/workflows/step-generation-test.yaml | 38 ++++-------- NOTICE | 2 +- step-generation/Makefile | 4 +- 4 files changed, 62 insertions(+), 48 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 4eb13688110..0ec55aa6462 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,27 +1,55 @@ +version: 2.0 + +coverage: + status: + project: + default: + target: auto + threshold: '10' + patch: + default: + target: auto + threshold: 0 + +comment: + layout: 'reach, diff, flags, files' + ignore: - - '**/node_modules' - - 'webpack-config' - - 'hardware-testing' - - '**/*.md' - - '**/*.yaml' + - 'webpack-config/**' + - 'hardware-testing/**' + - 'abr-testing/**' + - 'test-data-generation/**' + - 'performance-metrics/**' + - 'package-testing/**' + - 'opentrons-ai-server/**' + - 'opentrons-ai-client/**' + - 'g-code-testing/**' + - 'api-client/**' + - 'analyses-snapshot-testing/**' + - '.storybook/**' + - 'react-api-client/**' + - 'scripts/**' + - '.github/**' + - '**/build/**' + - '**/dist/**' + - '**/node_modules/**' + - '**/{test,tests,__tests__,__mocks__,mocks}/**' + - '**/*.{md,yaml,yml,json,rst}' - '**/Makefile' - - '**/*.in' - - '**/*.json' - - '**/*.config.js' + - '**/*.{in,ini,lock,toml,cfg}' + - '**/setup.py' + - '**/requirements.txt' + - '**/.flake8' + - '**/.coveragerc' + - '**/.prettierrc.js' + - '**/.eslintrc.js' + - '**/Pipfile' + - '**/Dockerfile*' + - '**/*.{config.js,config.ts}' - '**/*.mk' - '**/*.stories.tsx' - -comment: - # add flags to `layout` configuration to show up in the PR comment - layout: 'reach, diff, flags, files' + - '**/*.{mjs,css,cjs,mts}' flag_management: default_rules: carryforward: true - -coverage: - status: - project: - default: - target: auto - threshold: 10 diff --git a/.github/workflows/step-generation-test.yaml b/.github/workflows/step-generation-test.yaml index 4dd540eccb8..c589223e452 100644 --- a/.github/workflows/step-generation-test.yaml +++ b/.github/workflows/step-generation-test.yaml @@ -9,12 +9,18 @@ on: - 'shared-data/**' - 'package.json' - '.github/workflows/step-generation-test.yaml' + - '.github/actions/js/setup/action.yml' + - '.github/actions/git/resolve-tag/action.yml' + - '.github/actions/environment/complex-variables/action.yml' push: paths: - 'step-generation/**' - 'shared-data/**' - 'package.json' - '.github/workflows/step-generation-test.yaml' + - '.github/actions/js/setup/action.yml' + - '.github/actions/git/resolve-tag/action.yml' + - '.github/actions/environment/complex-variables/action.yml' branches: - '*' @@ -33,35 +39,15 @@ jobs: js-unit-test: name: 'step generation unit tests' runs-on: 'ubuntu-24.04' - timeout-minutes: 30 + timeout-minutes: 20 steps: - - uses: 'actions/checkout@v4' - - uses: 'actions/setup-node@v4' - with: - node-version: '22.11.0' - - name: 'install udev for usb-detection' - run: | - # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved - sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list - sudo apt-get update && sudo apt-get install libudev-dev - - name: 'cache yarn cache' - uses: actions/cache@v3 - with: - path: | - ${{ github.workspace }}/.yarn-cache - ${{ github.workspace }}/.npm-cache - key: js-${{ secrets.GH_CACHE_VERSION }}-${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} - restore-keys: | - js-${{ secrets.GH_CACHE_VERSION }}-${{ runner.os }}-yarn- - - name: 'setup-js' - run: | - npm config set cache ./.npm-cache - yarn config set cache-folder ./.yarn-cache - make setup-js + - name: 'Checkout Repository' + uses: actions/checkout@v4 + - uses: ./.github/actions/js/setup - name: 'run step generation unit tests' run: make -C step-generation test-cov - name: 'Upload coverage report' - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v5 with: - files: ./coverage/lcov.info flags: step-generation + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/NOTICE b/NOTICE index 3d2de27433a..88a30e2a338 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ Opentrons Platform -Copyright 2015-2020 Opentrons Labworks, Inc. +Copyright 2015-2025 Opentrons Labworks, Inc. The source code in this repository is licensed under the Apache License, Version 2.0 (the "License"); you may not use this code except in diff --git a/step-generation/Makefile b/step-generation/Makefile index fb32b39b756..9d3d98b40bf 100644 --- a/step-generation/Makefile +++ b/step-generation/Makefile @@ -3,10 +3,10 @@ # using bash instead of /bin/bash in SHELL prevents macOS optimizing away our PATH update SHELL := bash -# These variables can be overriden when make is invoked to customize the +# These variables can be overridden when make is invoked to customize the # behavior of jest tests ?= -cov_opts ?= --coverage=true +cov_opts ?= --coverage --pool=threads test_opts ?= .PHONY: test