From 69ee8f3497dc9c0d652d60b25a120c10e25ab80b Mon Sep 17 00:00:00 2001 From: Max Marrone Date: Mon, 16 Sep 2024 10:43:14 -0400 Subject: [PATCH 01/24] refactor(api): Move pipette location updates into command implementations (#16160) ## Overview This accomplishes a chunk of EXEC-652. It also sets the pattern for EXEC-639 in general, at least to start with. ## Test Plan and Hands on Testing * [x] Run some protocols in general on a real robot and make sure nothing weird happens with path planning. * [x] Check analysis snapshot tests to make sure there are no changes. ## Changelog * When a command succeeds, or fails with a defined error, it now passes a new piece of data to engine internals: the `state_update`. The `state_update` lets you express common things that commands do, like "set pipette `abc123`'s logical location to well `A1` in labware `def456`". The `StateStore`s listen for `state_updates` and perform the actual modifications. There are some convenience methods to help command implementations build up a `StateUpdate` step by step, as they physically affect the robot in different ways. This is done in a way that retains the fundamental behavior of Protocol Engine where state is only changed in between commands, not during them. * Partially port a handful of commands. They now use `state_update` to update the pipette location, instead of `PipetteStore` doing stuff like `isinstance(command, MoveToWell)`. The follow-up work is to keep going with more commands, and more kinds of state, like liquid amounts. ## Risk assessment High. This is intrusive to how protocols execute, e.g. it affects path planning. It's difficult to test that the overall integrated behavior is preserved. G-code tests cover this a bit, but I don't think those have kept up with new commands. --- .../protocol_engine/actions/actions.py | 56 +- .../protocol_engine/commands/aspirate.py | 24 +- .../commands/aspirate_in_place.py | 12 +- .../protocol_engine/commands/blow_out.py | 15 +- .../protocol_engine/commands/command.py | 33 +- .../commands/command_unions.py | 6 +- .../protocol_engine/commands/dispense.py | 27 +- .../commands/dispense_in_place.py | 12 +- .../protocol_engine/commands/drop_tip.py | 15 +- .../protocol_engine/commands/move_to_well.py | 14 +- .../protocol_engine/commands/pick_up_tip.py | 35 +- .../commands/pipetting_common.py | 16 +- .../execution/command_executor.py | 4 +- .../protocol_engine/state/pipettes.py | 128 +++-- .../protocol_engine/state/update_types.py | 120 ++++ .../protocol_runner/legacy_command_mapper.py | 10 +- .../protocol_engine/commands/test_aspirate.py | 42 +- .../commands/test_aspirate_in_place.py | 10 +- .../protocol_engine/commands/test_blow_out.py | 14 +- .../protocol_engine/commands/test_dispense.py | 28 +- .../commands/test_dispense_in_place.py | 10 +- .../protocol_engine/commands/test_drop_tip.py | 27 +- .../commands/test_move_to_well.py | 11 +- .../commands/test_pick_up_tip.py | 28 +- .../state/test_pipette_store.py | 526 +++++------------- 25 files changed, 641 insertions(+), 582 deletions(-) create mode 100644 api/src/opentrons/protocol_engine/state/update_types.py diff --git a/api/src/opentrons/protocol_engine/actions/actions.py b/api/src/opentrons/protocol_engine/actions/actions.py index 38cbbe18bb3..4569f7866ef 100644 --- a/api/src/opentrons/protocol_engine/actions/actions.py +++ b/api/src/opentrons/protocol_engine/actions/actions.py @@ -3,7 +3,7 @@ Actions can be passed to the ActionDispatcher, where they will trigger reactions in objects that subscribe to the pipeline, like the StateStore. """ -from dataclasses import dataclass +import dataclasses from datetime import datetime from enum import Enum from typing import List, Optional, Union @@ -22,6 +22,7 @@ ) from ..error_recovery_policy import ErrorRecoveryPolicy, ErrorRecoveryType from ..notes.notes import CommandNote +from ..state.update_types import StateUpdate from ..types import ( LabwareOffsetCreate, ModuleDefinition, @@ -31,7 +32,7 @@ ) -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class PlayAction: """Start or resume processing commands in the engine.""" @@ -50,28 +51,28 @@ class PauseSource(str, Enum): PROTOCOL = "protocol" -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class PauseAction: """Pause processing commands in the engine.""" source: PauseSource -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class StopAction: """Request engine execution to stop soon.""" from_estop: bool = False -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class ResumeFromRecoveryAction: """See `ProtocolEngine.resume_from_recovery()`.""" pass -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class FinishErrorDetails: """Error details for the payload of a FinishAction or HardwareStoppedAction.""" @@ -80,7 +81,7 @@ class FinishErrorDetails: created_at: datetime -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class FinishAction: """Gracefully stop processing commands in the engine.""" @@ -95,7 +96,7 @@ class FinishAction: """The fatal error that caused the run to fail.""" -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class HardwareStoppedAction: """An action dispatched after hardware has been stopped for good, for this engine instance.""" @@ -105,14 +106,14 @@ class HardwareStoppedAction: """The error that happened while doing post-run finish steps (homing and dropping tips).""" -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class DoorChangeAction: """Handle events coming in from hardware control.""" door_state: DoorState -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class QueueCommandAction: """Add a command request to the queue.""" @@ -123,7 +124,7 @@ class QueueCommandAction: failed_command_id: Optional[str] = None -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class RunCommandAction: """Mark a given command as running. @@ -135,7 +136,7 @@ class RunCommandAction: started_at: datetime -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class SucceedCommandAction: """Mark a given command as succeeded. @@ -145,10 +146,19 @@ class SucceedCommandAction: command: Command """The command in its new succeeded state.""" + # todo(mm, 2024-08-26): Remove when no state stores use this anymore. + # https://opentrons.atlassian.net/browse/EXEC-639 private_result: CommandPrivateResult + state_update: StateUpdate = dataclasses.field( + # todo(mm, 2024-08-26): This has a default only to make it easier to transition + # old tests while https://opentrons.atlassian.net/browse/EXEC-639 is in + # progress. Make this mandatory when that's completed. + default_factory=StateUpdate + ) -@dataclass(frozen=True) + +@dataclasses.dataclass(frozen=True) class FailCommandAction: """Mark a given command as failed. @@ -196,7 +206,7 @@ class FailCommandAction: """The command to fail, in its prior `running` state.""" -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class AddLabwareOffsetAction: """Add a labware offset, to apply to subsequent `LoadLabwareCommand`s.""" @@ -205,28 +215,28 @@ class AddLabwareOffsetAction: request: LabwareOffsetCreate -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class AddLabwareDefinitionAction: """Add a labware definition, to apply to subsequent `LoadLabwareCommand`s.""" definition: LabwareDefinition -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class AddLiquidAction: """Add a liquid, to apply to subsequent `LoadLiquid`s.""" liquid: Liquid -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class SetDeckConfigurationAction: """See `ProtocolEngine.set_deck_configuration()`.""" deck_configuration: Optional[DeckConfigurationType] -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class AddAddressableAreaAction: """Add a single addressable area to state. @@ -238,7 +248,7 @@ class AddAddressableAreaAction: addressable_area: AddressableAreaLocation -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class AddModuleAction: """Add an attached module directly to state without a location.""" @@ -248,14 +258,14 @@ class AddModuleAction: module_live_data: LiveData -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class ResetTipsAction: """Reset the tip tracking state of a given tip rack.""" labware_id: str -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class SetPipetteMovementSpeedAction: """Set the speed of a pipette's X/Y/Z movements. Does not affect plunger speed. @@ -266,7 +276,7 @@ class SetPipetteMovementSpeedAction: speed: Optional[float] -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class AddAbsorbanceReaderLidAction: """Add the absorbance reader lid id to the absorbance reader module substate. @@ -277,7 +287,7 @@ class AddAbsorbanceReaderLidAction: lid_id: str -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class SetErrorRecoveryPolicyAction: """See `ProtocolEngine.set_error_recovery_policy()`.""" diff --git a/api/src/opentrons/protocol_engine/commands/aspirate.py b/api/src/opentrons/protocol_engine/commands/aspirate.py index 22984fe0d13..4e5e7d52260 100644 --- a/api/src/opentrons/protocol_engine/commands/aspirate.py +++ b/api/src/opentrons/protocol_engine/commands/aspirate.py @@ -6,7 +6,6 @@ from .pipetting_common import ( OverpressureError, - OverpressureErrorInternalData, PipetteIdMixin, AspirateVolumeMixin, FlowRateMixin, @@ -25,6 +24,7 @@ from opentrons.hardware_control import HardwareControlAPI +from ..state.update_types import StateUpdate from ..types import WellLocation, WellOrigin, CurrentWell, DeckPoint if TYPE_CHECKING: @@ -53,7 +53,7 @@ class AspirateResult(BaseLiquidHandlingResult, DestinationPositionResult): _ExecuteReturn = Union[ SuccessData[AspirateResult, None], - DefinedErrorData[OverpressureError, OverpressureErrorInternalData], + DefinedErrorData[OverpressureError, None], ] @@ -92,6 +92,7 @@ async def execute(self, params: AspirateParams) -> _ExecuteReturn: ) current_well = None + state_update = StateUpdate() if not ready_to_aspirate: await self._movement.move_to_well( @@ -118,6 +119,13 @@ async def execute(self, params: AspirateParams) -> _ExecuteReturn: well_location=params.wellLocation, current_well=current_well, ) + deck_point = DeckPoint.construct(x=position.x, y=position.y, z=position.z) + state_update.set_pipette_location( + pipette_id=pipette_id, + new_labware_id=labware_id, + new_well_name=well_name, + new_deck_point=deck_point, + ) try: volume_aspirated = await self._pipetting.aspirate_in_place( @@ -140,21 +148,17 @@ async def execute(self, params: AspirateParams) -> _ExecuteReturn: ], errorInfo={"retryLocation": (position.x, position.y, position.z)}, ), - private=OverpressureErrorInternalData( - position=DeckPoint.construct( - x=position.x, y=position.y, z=position.z - ) - ), + private=None, + state_update=state_update, ) else: return SuccessData( public=AspirateResult( volume=volume_aspirated, - position=DeckPoint.construct( - x=position.x, y=position.y, z=position.z - ), + position=deck_point, ), private=None, + state_update=state_update, ) diff --git a/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py b/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py index 976457c1173..7c2e5ce67fd 100644 --- a/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py +++ b/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py @@ -14,7 +14,6 @@ FlowRateMixin, BaseLiquidHandlingResult, OverpressureError, - OverpressureErrorInternalData, ) from .command import ( AbstractCommandImpl, @@ -25,7 +24,6 @@ ) from ..errors.error_occurrence import ErrorOccurrence from ..errors.exceptions import PipetteNotReadyToAspirateError -from ..types import DeckPoint if TYPE_CHECKING: from ..execution import PipettingHandler, GantryMover @@ -50,7 +48,7 @@ class AspirateInPlaceResult(BaseLiquidHandlingResult): _ExecuteReturn = Union[ SuccessData[AspirateInPlaceResult, None], - DefinedErrorData[OverpressureError, OverpressureErrorInternalData], + DefinedErrorData[OverpressureError, None], ] @@ -123,13 +121,7 @@ async def execute(self, params: AspirateInPlaceParams) -> _ExecuteReturn: } ), ), - private=OverpressureErrorInternalData( - position=DeckPoint( - x=current_position.x, - y=current_position.y, - z=current_position.z, - ), - ), + private=None, ) else: return SuccessData( diff --git a/api/src/opentrons/protocol_engine/commands/blow_out.py b/api/src/opentrons/protocol_engine/commands/blow_out.py index 085580f6f8f..9954ef07cfa 100644 --- a/api/src/opentrons/protocol_engine/commands/blow_out.py +++ b/api/src/opentrons/protocol_engine/commands/blow_out.py @@ -3,6 +3,8 @@ from typing import TYPE_CHECKING, Optional, Type from typing_extensions import Literal + +from ..state.update_types import StateUpdate from ..types import DeckPoint from .pipetting_common import ( PipetteIdMixin, @@ -55,19 +57,30 @@ def __init__( async def execute(self, params: BlowOutParams) -> SuccessData[BlowOutResult, None]: """Move to and blow-out the requested well.""" + state_update = StateUpdate() + x, y, z = await self._movement.move_to_well( pipette_id=params.pipetteId, labware_id=params.labwareId, well_name=params.wellName, well_location=params.wellLocation, ) + deck_point = DeckPoint.construct(x=x, y=y, z=z) + state_update.set_pipette_location( + pipette_id=params.pipetteId, + new_labware_id=params.labwareId, + new_well_name=params.wellName, + new_deck_point=deck_point, + ) await self._pipetting.blow_out_in_place( pipette_id=params.pipetteId, flow_rate=params.flowRate ) return SuccessData( - public=BlowOutResult(position=DeckPoint(x=x, y=y, z=z)), private=None + public=BlowOutResult(position=deck_point), + private=None, + state_update=state_update, ) diff --git a/api/src/opentrons/protocol_engine/commands/command.py b/api/src/opentrons/protocol_engine/commands/command.py index 757cdfe488a..9a519425143 100644 --- a/api/src/opentrons/protocol_engine/commands/command.py +++ b/api/src/opentrons/protocol_engine/commands/command.py @@ -3,8 +3,8 @@ from __future__ import annotations +import dataclasses from abc import ABC, abstractmethod -from dataclasses import dataclass from datetime import datetime from enum import Enum from typing import ( @@ -21,6 +21,7 @@ from pydantic.generics import GenericModel from opentrons.hardware_control import HardwareControlAPI +from opentrons.protocol_engine.state.update_types import StateUpdate from ..resources import ModelUtils from ..errors import ErrorOccurrence @@ -106,7 +107,7 @@ class BaseCommandCreate( ) -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class SuccessData(Generic[_ResultT_co, _PrivateResultT_co]): """Data from the successful completion of a command.""" @@ -114,10 +115,21 @@ class SuccessData(Generic[_ResultT_co, _PrivateResultT_co]): """Public result data. Exposed over HTTP and stored in databases.""" private: _PrivateResultT_co - """Additional result data, only given to `opentrons.protocol_engine` internals.""" + """Additional result data, only given to `opentrons.protocol_engine` internals. + + Deprecated: + Use `state_update` instead. + """ + + state_update: StateUpdate = dataclasses.field( + # todo(mm, 2024-08-22): Remove the default once all command implementations + # use this, to make it harder to forget in new command implementations. + default_factory=StateUpdate + ) + """How the engine state should be updated to reflect this command success.""" -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class DefinedErrorData(Generic[_ErrorT_co, _PrivateResultT_co]): """Data from a command that failed with a defined error. @@ -129,7 +141,18 @@ class DefinedErrorData(Generic[_ErrorT_co, _PrivateResultT_co]): """Public error data. Exposed over HTTP and stored in databases.""" private: _PrivateResultT_co - """Additional error data, only given to `opentrons.protocol_engine` internals.""" + """Additional error data, only given to `opentrons.protocol_engine` internals. + + Deprecated: + Use `state_update` instead. + """ + + state_update: StateUpdate = dataclasses.field( + # todo(mm, 2024-08-22): Remove the default once all command implementations + # use this, to make it harder to forget in new command implementations. + default_factory=StateUpdate + ) + """How the engine state should be updated to reflect this command failure.""" class BaseCommand( diff --git a/api/src/opentrons/protocol_engine/commands/command_unions.py b/api/src/opentrons/protocol_engine/commands/command_unions.py index 44bddba1e7e..27e97976ad0 100644 --- a/api/src/opentrons/protocol_engine/commands/command_unions.py +++ b/api/src/opentrons/protocol_engine/commands/command_unions.py @@ -10,7 +10,6 @@ from .command import DefinedErrorData from .pipetting_common import ( OverpressureError, - OverpressureErrorInternalData, LiquidNotFoundError, LiquidNotFoundErrorInternalData, ) @@ -216,7 +215,6 @@ PickUpTipResult, PickUpTipCommandType, TipPhysicallyMissingError, - TipPhysicallyMissingErrorInternalData, ) from .touch_tip import ( @@ -706,8 +704,8 @@ # All `DefinedErrorData`s that implementations will actually return in practice. CommandDefinedErrorData = Union[ - DefinedErrorData[TipPhysicallyMissingError, TipPhysicallyMissingErrorInternalData], - DefinedErrorData[OverpressureError, OverpressureErrorInternalData], + DefinedErrorData[TipPhysicallyMissingError, None], + DefinedErrorData[OverpressureError, None], DefinedErrorData[LiquidNotFoundError, LiquidNotFoundErrorInternalData], ] diff --git a/api/src/opentrons/protocol_engine/commands/dispense.py b/api/src/opentrons/protocol_engine/commands/dispense.py index b346fb5845a..5ceef20f2bf 100644 --- a/api/src/opentrons/protocol_engine/commands/dispense.py +++ b/api/src/opentrons/protocol_engine/commands/dispense.py @@ -8,6 +8,7 @@ from pydantic import Field from ..types import DeckPoint +from ..state.update_types import StateUpdate from .pipetting_common import ( PipetteIdMixin, DispenseVolumeMixin, @@ -16,7 +17,6 @@ BaseLiquidHandlingResult, DestinationPositionResult, OverpressureError, - OverpressureErrorInternalData, ) from .command import ( AbstractCommandImpl, @@ -54,7 +54,7 @@ class DispenseResult(BaseLiquidHandlingResult, DestinationPositionResult): _ExecuteReturn = Union[ SuccessData[DispenseResult, None], - DefinedErrorData[OverpressureError, OverpressureErrorInternalData], + DefinedErrorData[OverpressureError, None], ] @@ -74,12 +74,22 @@ def __init__( async def execute(self, params: DispenseParams) -> _ExecuteReturn: """Move to and dispense to the requested well.""" + state_update = StateUpdate() + position = await self._movement.move_to_well( pipette_id=params.pipetteId, labware_id=params.labwareId, well_name=params.wellName, well_location=params.wellLocation, ) + deck_point = DeckPoint.construct(x=position.x, y=position.y, z=position.z) + state_update.set_pipette_location( + pipette_id=params.pipetteId, + new_labware_id=params.labwareId, + new_well_name=params.wellName, + new_deck_point=deck_point, + ) + try: volume = await self._pipetting.dispense_in_place( pipette_id=params.pipetteId, @@ -101,19 +111,14 @@ async def execute(self, params: DispenseParams) -> _ExecuteReturn: ], errorInfo={"retryLocation": (position.x, position.y, position.z)}, ), - private=OverpressureErrorInternalData( - position=DeckPoint.construct( - x=position.x, y=position.y, z=position.z - ) - ), + private=None, + state_update=state_update, ) else: return SuccessData( - public=DispenseResult( - volume=volume, - position=DeckPoint(x=position.x, y=position.y, z=position.z), - ), + public=DispenseResult(volume=volume, position=deck_point), private=None, + state_update=state_update, ) diff --git a/api/src/opentrons/protocol_engine/commands/dispense_in_place.py b/api/src/opentrons/protocol_engine/commands/dispense_in_place.py index d71f191d1df..1553d0e8ac1 100644 --- a/api/src/opentrons/protocol_engine/commands/dispense_in_place.py +++ b/api/src/opentrons/protocol_engine/commands/dispense_in_place.py @@ -12,7 +12,6 @@ FlowRateMixin, BaseLiquidHandlingResult, OverpressureError, - OverpressureErrorInternalData, ) from .command import ( AbstractCommandImpl, @@ -22,7 +21,6 @@ DefinedErrorData, ) from ..errors.error_occurrence import ErrorOccurrence -from ..types import DeckPoint if TYPE_CHECKING: from ..execution import PipettingHandler, GantryMover @@ -49,7 +47,7 @@ class DispenseInPlaceResult(BaseLiquidHandlingResult): _ExecuteReturn = Union[ SuccessData[DispenseInPlaceResult, None], - DefinedErrorData[OverpressureError, OverpressureErrorInternalData], + DefinedErrorData[OverpressureError, None], ] @@ -101,13 +99,7 @@ async def execute(self, params: DispenseInPlaceParams) -> _ExecuteReturn: } ), ), - private=OverpressureErrorInternalData( - position=DeckPoint( - x=current_position.x, - y=current_position.y, - z=current_position.z, - ), - ), + private=None, ) else: return SuccessData( diff --git a/api/src/opentrons/protocol_engine/commands/drop_tip.py b/api/src/opentrons/protocol_engine/commands/drop_tip.py index 381d2869ac2..416472fc440 100644 --- a/api/src/opentrons/protocol_engine/commands/drop_tip.py +++ b/api/src/opentrons/protocol_engine/commands/drop_tip.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Optional, Type from typing_extensions import Literal +from ..state import update_types from ..types import DropTipWellLocation, DeckPoint from .pipetting_common import PipetteIdMixin, DestinationPositionResult from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData @@ -76,6 +77,8 @@ async def execute(self, params: DropTipParams) -> SuccessData[DropTipResult, Non well_name = params.wellName home_after = params.homeAfter + state_update = update_types.StateUpdate() + if params.alternateDropLocation: well_location = self._state_view.geometry.get_next_tip_drop_location( labware_id=labware_id, @@ -101,14 +104,20 @@ async def execute(self, params: DropTipParams) -> SuccessData[DropTipResult, Non well_name=well_name, well_location=tip_drop_location, ) + deck_point = DeckPoint.construct(x=position.x, y=position.y, z=position.z) + state_update.set_pipette_location( + pipette_id=pipette_id, + new_labware_id=labware_id, + new_well_name=well_name, + new_deck_point=deck_point, + ) await self._tip_handler.drop_tip(pipette_id=pipette_id, home_after=home_after) return SuccessData( - public=DropTipResult( - position=DeckPoint(x=position.x, y=position.y, z=position.z) - ), + public=DropTipResult(position=deck_point), private=None, + state_update=state_update, ) diff --git a/api/src/opentrons/protocol_engine/commands/move_to_well.py b/api/src/opentrons/protocol_engine/commands/move_to_well.py index 2ed10757b69..9695ccb3bc0 100644 --- a/api/src/opentrons/protocol_engine/commands/move_to_well.py +++ b/api/src/opentrons/protocol_engine/commands/move_to_well.py @@ -12,6 +12,7 @@ ) from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData from ..errors.error_occurrence import ErrorOccurrence +from ..state import update_types if TYPE_CHECKING: from ..execution import MovementHandler @@ -43,6 +44,8 @@ async def execute( self, params: MoveToWellParams ) -> SuccessData[MoveToWellResult, None]: """Move the requested pipette to the requested well.""" + state_update = update_types.StateUpdate() + x, y, z = await self._movement.move_to_well( pipette_id=params.pipetteId, labware_id=params.labwareId, @@ -52,9 +55,18 @@ async def execute( minimum_z_height=params.minimumZHeight, speed=params.speed, ) + deck_point = DeckPoint.construct(x=x, y=y, z=z) + state_update.set_pipette_location( + pipette_id=params.pipetteId, + new_labware_id=params.labwareId, + new_well_name=params.wellName, + new_deck_point=deck_point, + ) return SuccessData( - public=MoveToWellResult(position=DeckPoint(x=x, y=y, z=z)), private=None + public=MoveToWellResult(position=deck_point), + private=None, + state_update=state_update, ) diff --git a/api/src/opentrons/protocol_engine/commands/pick_up_tip.py b/api/src/opentrons/protocol_engine/commands/pick_up_tip.py index 62b8dcedc9b..caa16866528 100644 --- a/api/src/opentrons/protocol_engine/commands/pick_up_tip.py +++ b/api/src/opentrons/protocol_engine/commands/pick_up_tip.py @@ -1,15 +1,14 @@ """Pick up tip command request, result, and implementation models.""" from __future__ import annotations -from dataclasses import dataclass from opentrons_shared_data.errors import ErrorCodes from pydantic import Field from typing import TYPE_CHECKING, Optional, Type, Union from typing_extensions import Literal -from opentrons.protocol_engine.errors.exceptions import TipNotAttachedError -from ..errors import ErrorOccurrence +from ..errors import ErrorOccurrence, TipNotAttachedError from ..resources import ModelUtils +from ..state import update_types from ..types import DeckPoint from .pipetting_common import ( PipetteIdMixin, @@ -78,18 +77,9 @@ class TipPhysicallyMissingError(ErrorOccurrence): detail: str = "No tip detected." -@dataclass(frozen=True) -class TipPhysicallyMissingErrorInternalData: - """Internal-to-ProtocolEngine data about a TipPhysicallyMissingError.""" - - pipette_id: str - labware_id: str - well_name: str - - _ExecuteReturn = Union[ SuccessData[PickUpTipResult, None], - DefinedErrorData[TipPhysicallyMissingError, TipPhysicallyMissingErrorInternalData], + DefinedErrorData[TipPhysicallyMissingError, None], ] @@ -118,12 +108,21 @@ async def execute( well_name = params.wellName well_location = params.wellLocation + state_update = update_types.StateUpdate() + position = await self._movement.move_to_well( pipette_id=pipette_id, labware_id=labware_id, well_name=well_name, well_location=well_location, ) + deck_point = DeckPoint.construct(x=position.x, y=position.y, z=position.z) + state_update.set_pipette_location( + pipette_id=pipette_id, + new_labware_id=labware_id, + new_well_name=well_name, + new_deck_point=deck_point, + ) try: tip_geometry = await self._tip_handler.pick_up_tip( @@ -144,11 +143,8 @@ async def execute( ) ], ), - private=TipPhysicallyMissingErrorInternalData( - pipette_id=pipette_id, - labware_id=labware_id, - well_name=well_name, - ), + private=None, + state_update=state_update, ) else: return SuccessData( @@ -156,9 +152,10 @@ async def execute( tipVolume=tip_geometry.volume, tipLength=tip_geometry.length, tipDiameter=tip_geometry.diameter, - position=DeckPoint(x=position.x, y=position.y, z=position.z), + position=deck_point, ), private=None, + state_update=state_update, ) diff --git a/api/src/opentrons/protocol_engine/commands/pipetting_common.py b/api/src/opentrons/protocol_engine/commands/pipetting_common.py index 2be1e6f2d54..0c6f463ed5d 100644 --- a/api/src/opentrons/protocol_engine/commands/pipetting_common.py +++ b/api/src/opentrons/protocol_engine/commands/pipetting_common.py @@ -114,6 +114,14 @@ class BaseLiquidHandlingResult(BaseModel): class DestinationPositionResult(BaseModel): """Mixin for command results that move a pipette.""" + # todo(mm, 2024-08-02): Consider deprecating or redefining this. + # + # This is here because opentrons.protocol_engine needed it for internal bookkeeping + # and, at the time, we didn't have a way to do that without adding this to the + # public command results. Its usefulness to callers outside + # opentrons.protocol_engine is questionable because they would need to know which + # critical point is in play, and I think that can change depending on obscure + # things like labware quirks. position: DeckPoint = Field( DeckPoint(x=0, y=0, z=0), description=( @@ -149,14 +157,6 @@ class OverpressureError(ErrorOccurrence): errorInfo: ErrorLocationInfo -@dataclass(frozen=True) -class OverpressureErrorInternalData: - """Internal-to-ProtocolEngine data about an OverpressureError.""" - - position: DeckPoint - """Same meaning as DestinationPositionResult.position.""" - - class LiquidNotFoundError(ErrorOccurrence): """Returned when no liquid is detected during the liquid probe process/move. diff --git a/api/src/opentrons/protocol_engine/execution/command_executor.py b/api/src/opentrons/protocol_engine/execution/command_executor.py index e014783c23c..e9dd2ec73b9 100644 --- a/api/src/opentrons/protocol_engine/execution/command_executor.py +++ b/api/src/opentrons/protocol_engine/execution/command_executor.py @@ -185,7 +185,9 @@ async def execute(self, command_id: str) -> None: succeeded_command = running_command.copy(update=update) self._action_dispatcher.dispatch( SucceedCommandAction( - command=succeeded_command, private_result=result.private + command=succeeded_command, + private_result=result.private, + state_update=result.state_update, ), ) else: diff --git a/api/src/opentrons/protocol_engine/state/pipettes.py b/api/src/opentrons/protocol_engine/state/pipettes.py index e6a407ef562..21d2dd6e114 100644 --- a/api/src/opentrons/protocol_engine/state/pipettes.py +++ b/api/src/opentrons/protocol_engine/state/pipettes.py @@ -1,9 +1,18 @@ """Basic pipette data state and store.""" from __future__ import annotations -from dataclasses import dataclass -from typing import Dict, List, Mapping, Optional, Tuple, Union + +import dataclasses +from typing import ( + Dict, + List, + Mapping, + Optional, + Tuple, + Union, +) from typing_extensions import assert_type +from opentrons_shared_data.errors import EnumeratedError from opentrons_shared_data.pipette import pipette_definition from opentrons.config.defaults_ot2 import Z_RETRACT_DISTANCE from opentrons.hardware_control.dev_types import PipetteDict @@ -13,13 +22,10 @@ ) from opentrons.protocol_engine.actions.actions import FailCommandAction from opentrons.protocol_engine.commands.command import DefinedErrorData -from opentrons.protocol_engine.commands.pipetting_common import ( - LiquidNotFoundError, - OverpressureError, - OverpressureErrorInternalData, -) +from opentrons.protocol_engine.commands.pipetting_common import LiquidNotFoundError from opentrons.types import MountType, Mount as HwMount, Point +from . import update_types from .. import commands from .. import errors from ..types import ( @@ -44,7 +50,7 @@ from ._abstract_store import HasState, HandlesActions -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class HardwarePipette: """Hardware pipette data.""" @@ -52,7 +58,7 @@ class HardwarePipette: config: PipetteDict -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class CurrentDeckPoint: """The latest deck point and mount the robot has accessed.""" @@ -60,7 +66,7 @@ class CurrentDeckPoint: deck_point: Optional[DeckPoint] -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class BoundingNozzlesOffsets: """Offsets of the bounding nozzles of the pipette.""" @@ -68,7 +74,7 @@ class BoundingNozzlesOffsets: front_right_offset: Point -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class PipetteBoundingBoxOffsets: """Offsets of the corners of the pipette's bounding box.""" @@ -78,7 +84,7 @@ class PipetteBoundingBoxOffsets: front_left_corner: Point -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class StaticPipetteConfig: """Static config for a pipette.""" @@ -100,7 +106,7 @@ class StaticPipetteConfig: lld_settings: Optional[Dict[str, Dict[str, float]]] -@dataclass +@dataclasses.dataclass class PipetteState: """Basic pipette data state and getter methods.""" @@ -279,17 +285,43 @@ def _handle_command( # noqa: C901 def _update_current_location( # noqa: C901 self, action: Union[SucceedCommandAction, FailCommandAction] ) -> None: + if isinstance(action, SucceedCommandAction): + location_update = action.state_update.pipette_location + elif isinstance(action.error, DefinedErrorData): + location_update = action.error.state_update.pipette_location + else: + # The command failed with some undefined error. We have nothing to do. + assert_type(action.error, EnumeratedError) + return + + if location_update != update_types.NO_CHANGE: + match location_update.new_location: + case update_types.Well(labware_id=labware_id, well_name=well_name): + self._state.current_location = CurrentWell( + pipette_id=location_update.pipette_id, + labware_id=labware_id, + well_name=well_name, + ) + case update_types.AddressableArea( + addressable_area_name=addressable_area_name + ): + self._state.current_location = CurrentAddressableArea( + pipette_id=location_update.pipette_id, + addressable_area_name=addressable_area_name, + ) + case None: + self._state.current_location = None + case update_types.NO_CHANGE: + pass + + # todo(mm, 2024-08-29): Port the following isinstance() checks to + # use `state_update`. https://opentrons.atlassian.net/browse/EXEC-639 + # These commands leave the pipette in a new location. # Update current_location to reflect that. if isinstance(action, SucceedCommandAction) and isinstance( action.command.result, ( - commands.MoveToWellResult, - commands.PickUpTipResult, - commands.DropTipResult, - commands.AspirateResult, - commands.DispenseResult, - commands.BlowOutResult, commands.TouchTipResult, commands.LiquidProbeResult, commands.TryLiquidProbeResult, @@ -304,12 +336,6 @@ def _update_current_location( # noqa: C901 isinstance(action.error, DefinedErrorData) and ( ( - isinstance( - action.running_command, (commands.Aspirate, commands.Dispense) - ) - and isinstance(action.error.public, OverpressureError) - ) - or ( isinstance(action.running_command, commands.LiquidProbe) and isinstance(action.error.public, LiquidNotFoundError) ) @@ -380,25 +406,40 @@ def _update_current_location( # noqa: C901 ): self._state.current_location = None - def _update_deck_point( + def _update_deck_point( # noqa: C901 self, action: Union[SucceedCommandAction, FailCommandAction] ) -> None: - # This function mostly mirrors self._update_current_location(). + if isinstance(action, SucceedCommandAction): + location_update = action.state_update.pipette_location + elif isinstance(action.error, DefinedErrorData): + location_update = action.error.state_update.pipette_location + else: + # The command failed with some undefined error. We have nothing to do. + assert_type(action.error, EnumeratedError) + return + + if ( + location_update is not update_types.NO_CHANGE + and location_update.new_deck_point is not update_types.NO_CHANGE + ): + loaded_pipette = self._state.pipettes_by_id[location_update.pipette_id] + self._state.current_deck_point = CurrentDeckPoint( + mount=loaded_pipette.mount, deck_point=location_update.new_deck_point + ) + + # todo(mm, 2024-08-29): Port the following isinstance() checks to + # use `state_update`. https://opentrons.atlassian.net/browse/EXEC-639 + # + # These isinstance() checks mostly mirror self._update_current_location(). # See there for explanations. if isinstance(action, SucceedCommandAction) and isinstance( action.command.result, ( - commands.MoveToWellResult, commands.MoveToCoordinatesResult, commands.MoveRelativeResult, commands.MoveToAddressableAreaResult, commands.MoveToAddressableAreaForDropTipResult, - commands.PickUpTipResult, - commands.DropTipResult, - commands.AspirateResult, - commands.DispenseResult, - commands.BlowOutResult, commands.TouchTipResult, ), ): @@ -408,27 +449,6 @@ def _update_deck_point( self._state.current_deck_point = CurrentDeckPoint( mount=loaded_pipette.mount, deck_point=deck_point ) - elif ( - isinstance(action, FailCommandAction) - and isinstance( - action.running_command, - ( - commands.Aspirate, - commands.Dispense, - commands.AspirateInPlace, - commands.DispenseInPlace, - ), - ) - and isinstance(action.error, DefinedErrorData) - and isinstance(action.error.public, OverpressureError) - ): - assert_type(action.error.private, OverpressureErrorInternalData) - pipette_id = action.running_command.params.pipetteId - deck_point = action.error.private.position - loaded_pipette = self._state.pipettes_by_id[pipette_id] - self._state.current_deck_point = CurrentDeckPoint( - mount=loaded_pipette.mount, deck_point=deck_point - ) elif isinstance(action, SucceedCommandAction) and isinstance( action.command.result, diff --git a/api/src/opentrons/protocol_engine/state/update_types.py b/api/src/opentrons/protocol_engine/state/update_types.py new file mode 100644 index 00000000000..a71b1897b42 --- /dev/null +++ b/api/src/opentrons/protocol_engine/state/update_types.py @@ -0,0 +1,120 @@ +"""Structures to represent changes that commands want to make to engine state.""" + + +import dataclasses +import enum +import typing + +from opentrons.protocol_engine.types import DeckPoint + + +class _NoChangeEnum(enum.Enum): + NO_CHANGE = enum.auto() + + +NO_CHANGE: typing.Final = _NoChangeEnum.NO_CHANGE +"""A sentinel value to indicate that a value shouldn't be changed. + +Useful when `None` is semantically unclear or already has some other meaning. +""" + + +NoChangeType: typing.TypeAlias = typing.Literal[_NoChangeEnum.NO_CHANGE] +"""The type of `NO_CHANGE`, as `NoneType` is to `None`. + +Unfortunately, mypy doesn't let us write `Literal[NO_CHANGE]`. Use this instead. +""" + + +@dataclasses.dataclass(frozen=True) +class Well: + """Designates a well in a labware.""" + + labware_id: str + well_name: str + + +@dataclasses.dataclass(frozen=True) +class AddressableArea: + """Designates an addressable area.""" + + addressable_area_name: str + + +@dataclasses.dataclass +class PipetteLocationUpdate: + """Represents an update to perform on a pipette's location.""" + + pipette_id: str + + new_location: Well | AddressableArea | None | NoChangeType + """The pipette's new logical location. + + Note: `new_location=None` means "change the location to `None` (unknown)", + not "do not change the location". + """ + + new_deck_point: DeckPoint | NoChangeType + + +@dataclasses.dataclass +class StateUpdate: + """Represents an update to perform on engine state.""" + + # todo(mm, 2024-08-29): Extend this with something to represent clearing both the + # deck point and the logical location, for e.g. home commands. Consider an explicit + # `CLEAR` sentinel if `None` is confusing. + pipette_location: PipetteLocationUpdate | NoChangeType = NO_CHANGE + + # These convenience functions let the caller avoid the boilerplate of constructing a + # complicated dataclass tree, and they give us a + + @typing.overload + def set_pipette_location( + self, + *, + pipette_id: str, + new_labware_id: str, + new_well_name: str, + new_deck_point: DeckPoint, + ) -> None: + """Schedule a pipette's location to be set to a well.""" + + @typing.overload + def set_pipette_location( + self, + *, + pipette_id: str, + new_addressable_area_name: str, + new_deck_point: DeckPoint, + ) -> None: + """Schedule a pipette's location to be set to an addressable area.""" + pass + + def set_pipette_location( # noqa: D102 + self, + *, + pipette_id: str, + new_labware_id: str | NoChangeType = NO_CHANGE, + new_well_name: str | NoChangeType = NO_CHANGE, + new_addressable_area_name: str | NoChangeType = NO_CHANGE, + new_deck_point: DeckPoint, + ) -> None: + if new_addressable_area_name != NO_CHANGE: + self.pipette_location = PipetteLocationUpdate( + pipette_id=pipette_id, + new_location=AddressableArea( + addressable_area_name=new_addressable_area_name + ), + new_deck_point=new_deck_point, + ) + else: + # These asserts should always pass because of the overloads. + assert new_labware_id != NO_CHANGE + assert new_well_name != NO_CHANGE + + self.pipette_location = PipetteLocationUpdate( + pipette_id=pipette_id, + new_location=Well(labware_id=new_labware_id, well_name=new_well_name), + new_deck_point=new_deck_point, + ) diff --git a/api/src/opentrons/protocol_runner/legacy_command_mapper.py b/api/src/opentrons/protocol_runner/legacy_command_mapper.py index b744c03351c..2e46e64663c 100644 --- a/api/src/opentrons/protocol_runner/legacy_command_mapper.py +++ b/api/src/opentrons/protocol_runner/legacy_command_mapper.py @@ -34,6 +34,7 @@ ModuleDataProvider, pipette_data_provider, ) +from opentrons.protocol_engine.state.update_types import StateUpdate from opentrons_shared_data.labware.labware_definition import LabwareDefinition from opentrons_shared_data.errors import ErrorCodes, EnumeratedError, PythonException @@ -267,7 +268,9 @@ def map_command( # noqa: C901 ) results.append( pe_actions.SucceedCommandAction( - completed_command, private_result=None + completed_command, + private_result=None, + state_update=StateUpdate(), ) ) @@ -675,6 +678,7 @@ def _map_labware_load( succeed_action = pe_actions.SucceedCommandAction( command=succeeded_command, private_result=None, + state_update=StateUpdate(), ) self._command_count["LOAD_LABWARE"] = count + 1 @@ -741,6 +745,7 @@ def _map_instrument_load( succeed_action = pe_actions.SucceedCommandAction( command=succeeded_command, private_result=pipette_config_result, + state_update=StateUpdate(), ) self._command_count["LOAD_PIPETTE"] = count + 1 @@ -805,8 +810,7 @@ def _map_module_load( started_at=succeeded_command.startedAt, # type: ignore[arg-type] ) succeed_action = pe_actions.SucceedCommandAction( - command=succeeded_command, - private_result=None, + command=succeeded_command, private_result=None, state_update=StateUpdate() ) self._command_count["LOAD_MODULE"] = count + 1 diff --git a/api/tests/opentrons/protocol_engine/commands/test_aspirate.py b/api/tests/opentrons/protocol_engine/commands/test_aspirate.py index b5964059474..0b2401bb27d 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_aspirate.py +++ b/api/tests/opentrons/protocol_engine/commands/test_aspirate.py @@ -5,10 +5,8 @@ from decoy import matchers, Decoy import pytest -from opentrons.protocol_engine.commands.pipetting_common import ( - OverpressureError, - OverpressureErrorInternalData, -) +from opentrons.protocol_engine.commands.pipetting_common import OverpressureError +from opentrons.protocol_engine.state import update_types from opentrons.types import MountType, Point from opentrons.protocol_engine import WellLocation, WellOrigin, WellOffset, DeckPoint @@ -98,6 +96,13 @@ async def test_aspirate_implementation_no_prep( assert result == SuccessData( public=AspirateResult(volume=50, position=DeckPoint(x=1, y=2, z=3)), private=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="abc", + new_location=update_types.Well(labware_id="123", well_name="A3"), + new_deck_point=DeckPoint(x=1, y=2, z=3), + ) + ), ) @@ -157,6 +162,13 @@ async def test_aspirate_implementation_with_prep( assert result == SuccessData( public=AspirateResult(volume=50, position=DeckPoint(x=1, y=2, z=3)), private=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="abc", + new_location=update_types.Well(labware_id="123", well_name="A3"), + new_deck_point=DeckPoint(x=1, y=2, z=3), + ) + ), ) decoy.verify( @@ -173,6 +185,7 @@ async def test_aspirate_implementation_with_prep( async def test_aspirate_raises_volume_error( decoy: Decoy, pipetting: PipettingHandler, + movement: MovementHandler, mock_command_note_adder: CommandNoteAdder, subject: AspirateImplementation, ) -> None: @@ -190,6 +203,16 @@ async def test_aspirate_raises_volume_error( decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id="abc")).then_return(True) + decoy.when( + await movement.move_to_well( + pipette_id="abc", + labware_id="123", + well_name="A3", + well_location=location, + current_well=None, + ), + ).then_return(Point(1, 2, 3)) + decoy.when( await pipetting.aspirate_in_place( pipette_id="abc", @@ -268,7 +291,14 @@ async def test_overpressure_error( wrappedErrors=[matchers.Anything()], errorInfo={"retryLocation": (position.x, position.y, position.z)}, ), - private=OverpressureErrorInternalData( - position=DeckPoint(x=position.x, y=position.y, z=position.z) + private=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id=pipette_id, + new_location=update_types.Well( + labware_id=labware_id, well_name=well_name + ), + new_deck_point=DeckPoint(x=position.x, y=position.y, z=position.z), + ) ), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py b/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py index c82adcc76d4..77dfde9deaf 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py +++ b/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py @@ -20,11 +20,7 @@ from opentrons.protocol_engine.notes import CommandNoteAdder from opentrons.protocol_engine.resources import ModelUtils from opentrons.protocol_engine.state.state import StateStore -from opentrons.protocol_engine.types import DeckPoint -from opentrons.protocol_engine.commands.pipetting_common import ( - OverpressureError, - OverpressureErrorInternalData, -) +from opentrons.protocol_engine.commands.pipetting_common import OverpressureError @pytest.fixture @@ -205,7 +201,5 @@ async def test_overpressure_error( wrappedErrors=[matchers.Anything()], errorInfo={"retryLocation": (position.x, position.y, position.z)}, ), - private=OverpressureErrorInternalData( - position=DeckPoint(x=position.x, y=position.y, z=position.z) - ), + private=None, ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_blow_out.py b/api/tests/opentrons/protocol_engine/commands/test_blow_out.py index a94d68d8de2..d762d18096e 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_blow_out.py +++ b/api/tests/opentrons/protocol_engine/commands/test_blow_out.py @@ -3,6 +3,7 @@ from opentrons.types import Point from opentrons.protocol_engine import WellLocation, WellOrigin, WellOffset, DeckPoint +from opentrons.protocol_engine.state import update_types from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.commands import ( BlowOutResult, @@ -54,7 +55,18 @@ async def test_blow_out_implementation( result = await subject.execute(data) assert result == SuccessData( - public=BlowOutResult(position=DeckPoint(x=1, y=2, z=3)), private=None + public=BlowOutResult(position=DeckPoint(x=1, y=2, z=3)), + private=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=update_types.Well( + labware_id="labware-id", + well_name="C6", + ), + new_deck_point=DeckPoint(x=1, y=2, z=3), + ) + ), ) decoy.verify( diff --git a/api/tests/opentrons/protocol_engine/commands/test_dispense.py b/api/tests/opentrons/protocol_engine/commands/test_dispense.py index 86c4f6ac93b..6280d19f8fa 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_dispense.py +++ b/api/tests/opentrons/protocol_engine/commands/test_dispense.py @@ -8,6 +8,7 @@ from opentrons.protocol_engine import WellLocation, WellOrigin, WellOffset, DeckPoint from opentrons.protocol_engine.execution import MovementHandler, PipettingHandler +from opentrons.protocol_engine.state import update_types from opentrons.types import Point from opentrons.protocol_engine.commands.command import SuccessData, DefinedErrorData @@ -17,10 +18,7 @@ DispenseImplementation, ) from opentrons.protocol_engine.resources.model_utils import ModelUtils -from opentrons.protocol_engine.commands.pipetting_common import ( - OverpressureError, - OverpressureErrorInternalData, -) +from opentrons.protocol_engine.commands.pipetting_common import OverpressureError @pytest.fixture @@ -75,6 +73,16 @@ async def test_dispense_implementation( assert result == SuccessData( public=DispenseResult(volume=42, position=DeckPoint(x=1, y=2, z=3)), private=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id-abc123", + new_location=update_types.Well( + labware_id="labware-id-abc123", + well_name="A3", + ), + new_deck_point=DeckPoint.construct(x=1, y=2, z=3), + ), + ), ) @@ -134,7 +142,15 @@ async def test_overpressure_error( wrappedErrors=[matchers.Anything()], errorInfo={"retryLocation": (position.x, position.y, position.z)}, ), - private=OverpressureErrorInternalData( - position=DeckPoint(x=position.x, y=position.y, z=position.z) + private=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=update_types.Well( + labware_id="labware-id", + well_name="well-name", + ), + new_deck_point=DeckPoint.construct(x=1, y=2, z=3), + ), ), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_dispense_in_place.py b/api/tests/opentrons/protocol_engine/commands/test_dispense_in_place.py index 3b37e1078b7..4a6d52a635b 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_dispense_in_place.py +++ b/api/tests/opentrons/protocol_engine/commands/test_dispense_in_place.py @@ -14,11 +14,7 @@ DispenseInPlaceResult, DispenseInPlaceImplementation, ) -from opentrons.protocol_engine.types import DeckPoint -from opentrons.protocol_engine.commands.pipetting_common import ( - OverpressureError, - OverpressureErrorInternalData, -) +from opentrons.protocol_engine.commands.pipetting_common import OverpressureError from opentrons.protocol_engine.resources import ModelUtils @@ -97,7 +93,5 @@ async def test_overpressure_error( wrappedErrors=[matchers.Anything()], errorInfo={"retryLocation": (position.x, position.y, position.z)}, ), - private=OverpressureErrorInternalData( - position=DeckPoint(x=position.x, y=position.y, z=position.z) - ), + private=None, ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_drop_tip.py b/api/tests/opentrons/protocol_engine/commands/test_drop_tip.py index eb2ff5cde09..d0d69eeccfa 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_drop_tip.py +++ b/api/tests/opentrons/protocol_engine/commands/test_drop_tip.py @@ -9,6 +9,7 @@ WellOffset, DeckPoint, ) +from opentrons.protocol_engine.state import update_types from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import MovementHandler, TipHandler from opentrons.types import Point @@ -112,7 +113,18 @@ async def test_drop_tip_implementation( result = await subject.execute(params) assert result == SuccessData( - public=DropTipResult(position=DeckPoint(x=111, y=222, z=333)), private=None + public=DropTipResult(position=DeckPoint(x=111, y=222, z=333)), + private=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="abc", + new_location=update_types.Well( + labware_id="123", + well_name="A3", + ), + new_deck_point=DeckPoint(x=111, y=222, z=333), + ) + ), ) decoy.verify( @@ -174,5 +186,16 @@ async def test_drop_tip_with_alternating_locations( result = await subject.execute(params) assert result == SuccessData( - public=DropTipResult(position=DeckPoint(x=111, y=222, z=333)), private=None + public=DropTipResult(position=DeckPoint(x=111, y=222, z=333)), + private=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="abc", + new_location=update_types.Well( + labware_id="123", + well_name="A3", + ), + new_deck_point=DeckPoint(x=111, y=222, z=333), + ) + ), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_move_to_well.py b/api/tests/opentrons/protocol_engine/commands/test_move_to_well.py index ddd6cf51a21..d91822979f2 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_move_to_well.py +++ b/api/tests/opentrons/protocol_engine/commands/test_move_to_well.py @@ -3,6 +3,7 @@ from opentrons.protocol_engine import WellLocation, WellOffset, DeckPoint from opentrons.protocol_engine.execution import MovementHandler +from opentrons.protocol_engine.state import update_types from opentrons.types import Point from opentrons.protocol_engine.commands.command import SuccessData @@ -45,5 +46,13 @@ async def test_move_to_well_implementation( result = await subject.execute(data) assert result == SuccessData( - public=MoveToWellResult(position=DeckPoint(x=9, y=8, z=7)), private=None + public=MoveToWellResult(position=DeckPoint(x=9, y=8, z=7)), + private=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="abc", + new_location=update_types.Well(labware_id="123", well_name="A3"), + new_deck_point=DeckPoint(x=9, y=8, z=7), + ) + ), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_pick_up_tip.py b/api/tests/opentrons/protocol_engine/commands/test_pick_up_tip.py index 67fb4b1c62a..06524b9e251 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_pick_up_tip.py +++ b/api/tests/opentrons/protocol_engine/commands/test_pick_up_tip.py @@ -9,6 +9,7 @@ from opentrons.protocol_engine.errors import TipNotAttachedError from opentrons.protocol_engine.execution import MovementHandler, TipHandler from opentrons.protocol_engine.resources import ModelUtils +from opentrons.protocol_engine.state import update_types from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.types import TipGeometry @@ -18,7 +19,6 @@ PickUpTipResult, PickUpTipImplementation, TipPhysicallyMissingError, - TipPhysicallyMissingErrorInternalData, ) @@ -73,6 +73,13 @@ async def test_success( position=DeckPoint(x=111, y=222, z=333), ), private=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=update_types.Well(labware_id="labware-id", well_name="A3"), + new_deck_point=DeckPoint(x=111, y=222, z=333), + ) + ), ) @@ -97,6 +104,14 @@ async def test_tip_physically_missing_error( error_id = "error-id" error_created_at = datetime(1234, 5, 6) + decoy.when( + await movement.move_to_well( + pipette_id="pipette-id", + labware_id="labware-id", + well_name="well-name", + well_location=WellLocation(offset=WellOffset()), + ) + ).then_return(Point(x=111, y=222, z=333)) decoy.when( await tip_handler.pick_up_tip( pipette_id=pipette_id, labware_id=labware_id, well_name=well_name @@ -113,7 +128,14 @@ async def test_tip_physically_missing_error( public=TipPhysicallyMissingError.construct( id=error_id, createdAt=error_created_at, wrappedErrors=[matchers.Anything()] ), - private=TipPhysicallyMissingErrorInternalData( - pipette_id=pipette_id, labware_id=labware_id, well_name=well_name + private=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=update_types.Well( + labware_id="labware-id", well_name="well-name" + ), + new_deck_point=DeckPoint(x=111, y=222, z=333), + ) ), ) diff --git a/api/tests/opentrons/protocol_engine/state/test_pipette_store.py b/api/tests/opentrons/protocol_engine/state/test_pipette_store.py index a49c9255605..ed9e9ad2576 100644 --- a/api/tests/opentrons/protocol_engine/state/test_pipette_store.py +++ b/api/tests/opentrons/protocol_engine/state/test_pipette_store.py @@ -6,17 +6,17 @@ from opentrons_shared_data.pipette.types import PipetteNameType from opentrons_shared_data.pipette import pipette_definition +from opentrons.protocol_engine.state import update_types from opentrons.types import DeckSlotName, MountType, Point from opentrons.protocol_engine import commands as cmd from opentrons.protocol_engine.commands.command import DefinedErrorData from opentrons.protocol_engine.commands.pipetting_common import ( LiquidNotFoundError, LiquidNotFoundErrorInternalData, - OverpressureError, - OverpressureErrorInternalData, ) from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryType from opentrons.protocol_engine.types import ( + CurrentAddressableArea, DeckPoint, DeckSlotLocation, LoadedPipette, @@ -52,6 +52,7 @@ create_pick_up_tip_command, create_drop_tip_command, create_drop_tip_in_place_command, + create_succeeded_command, create_unsafe_drop_tip_in_place_command, create_touch_tip_command, create_move_to_well_command, @@ -72,6 +73,35 @@ def subject() -> PipetteStore: return PipetteStore() +def _create_move_to_well_action( + pipette_id: str, + labware_id: str, + well_name: str, + deck_point: DeckPoint, +) -> SucceedCommandAction: + command = create_move_to_well_command( + pipette_id=pipette_id, + labware_id=labware_id, + well_name=well_name, + destination=deck_point, + ) + action = SucceedCommandAction( + command=command, + private_result=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id=pipette_id, + new_location=update_types.Well( + labware_id=labware_id, + well_name=well_name, + ), + new_deck_point=deck_point, + ) + ), + ) + return action + + def test_sets_initial_state(subject: PipetteStore) -> None: """It should initialize its state object properly.""" result = subject.state @@ -90,6 +120,85 @@ def test_sets_initial_state(subject: PipetteStore) -> None: ) +def test_location_state_update(subject: PipetteStore) -> None: + """It should update pipette locations.""" + load_command = create_load_pipette_command( + pipette_id="pipette-id", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.RIGHT, + ) + subject.handle_action( + SucceedCommandAction(command=load_command, private_result=None) + ) + + # Update the location to a well: + dummy_command = create_succeeded_command() + subject.handle_action( + SucceedCommandAction( + command=dummy_command, + private_result=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=update_types.Well( + labware_id="come on barbie", + well_name="let's go party", + ), + new_deck_point=DeckPoint(x=111, y=222, z=333), + ) + ), + ) + ) + assert subject.state.current_location == CurrentWell( + pipette_id="pipette-id", labware_id="come on barbie", well_name="let's go party" + ) + assert subject.state.current_deck_point == CurrentDeckPoint( + mount=MountType.RIGHT, deck_point=DeckPoint(x=111, y=222, z=333) + ) + + # Update the location to an addressable area: + subject.handle_action( + SucceedCommandAction( + command=dummy_command, + private_result=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=update_types.AddressableArea( + addressable_area_name="na na na na na" + ), + new_deck_point=DeckPoint(x=333, y=444, z=555), + ) + ), + ) + ) + assert subject.state.current_location == CurrentAddressableArea( + pipette_id="pipette-id", addressable_area_name="na na na na na" + ) + assert subject.state.current_deck_point == CurrentDeckPoint( + mount=MountType.RIGHT, deck_point=DeckPoint(x=333, y=444, z=555) + ) + + # Clear the logical location: + subject.handle_action( + SucceedCommandAction( + command=dummy_command, + private_result=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=None, + new_deck_point=update_types.NO_CHANGE, + ) + ), + ) + ) + assert subject.state.current_location is None + assert subject.state.current_deck_point == CurrentDeckPoint( + mount=MountType.RIGHT, deck_point=DeckPoint(x=333, y=444, z=555) + ) + + def test_handles_load_pipette(subject: PipetteStore) -> None: """It should add the pipette data to the state.""" command = create_load_pipette_command( @@ -335,175 +444,6 @@ def test_blow_out_clears_volume( @pytest.mark.parametrize( ("action", "expected_location"), ( - ( - SucceedCommandAction( - command=create_aspirate_command( - pipette_id="pipette-id", - labware_id="aspirate-labware-id", - well_name="aspirate-well-name", - volume=1337, - flow_rate=1.23, - ), - private_result=None, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="aspirate-labware-id", - well_name="aspirate-well-name", - ), - ), - ( - FailCommandAction( - running_command=cmd.Aspirate( - params=cmd.AspirateParams( - pipetteId="pipette-id", - labwareId="aspirate-labware-id", - wellName="aspirate-well-name", - volume=99999, - flowRate=1.23, - ), - id="command-id", - key="command-key", - createdAt=datetime.now(), - status=cmd.CommandStatus.RUNNING, - ), - error=DefinedErrorData( - public=OverpressureError( - id="error-id", - createdAt=datetime.now(), - errorInfo={"retryLocation": (0, 0, 0)}, - ), - private=OverpressureErrorInternalData( - position=DeckPoint(x=0, y=0, z=0) - ), - ), - command_id="command-id", - error_id="error-id", - failed_at=datetime.now(), - notes=[], - type=ErrorRecoveryType.WAIT_FOR_RECOVERY, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="aspirate-labware-id", - well_name="aspirate-well-name", - ), - ), - ( - SucceedCommandAction( - command=create_dispense_command( - pipette_id="pipette-id", - labware_id="dispense-labware-id", - well_name="dispense-well-name", - volume=1337, - flow_rate=1.23, - ), - private_result=None, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="dispense-labware-id", - well_name="dispense-well-name", - ), - ), - ( - SucceedCommandAction( - command=create_pick_up_tip_command( - pipette_id="pipette-id", - labware_id="pick-up-tip-labware-id", - well_name="pick-up-tip-well-name", - ), - private_result=None, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="pick-up-tip-labware-id", - well_name="pick-up-tip-well-name", - ), - ), - ( - SucceedCommandAction( - command=create_drop_tip_command( - pipette_id="pipette-id", - labware_id="drop-tip-labware-id", - well_name="drop-tip-well-name", - ), - private_result=None, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="drop-tip-labware-id", - well_name="drop-tip-well-name", - ), - ), - ( - SucceedCommandAction( - command=create_move_to_well_command( - pipette_id="pipette-id", - labware_id="move-to-well-labware-id", - well_name="move-to-well-well-name", - ), - private_result=None, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="move-to-well-labware-id", - well_name="move-to-well-well-name", - ), - ), - ( - SucceedCommandAction( - command=create_blow_out_command( - pipette_id="pipette-id", - labware_id="move-to-well-labware-id", - well_name="move-to-well-well-name", - flow_rate=1.23, - ), - private_result=None, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="move-to-well-labware-id", - well_name="move-to-well-well-name", - ), - ), - ( - FailCommandAction( - running_command=cmd.Dispense( - params=cmd.DispenseParams( - pipetteId="pipette-id", - labwareId="dispense-labware-id", - wellName="dispense-well-name", - volume=50, - flowRate=1.23, - ), - id="command-id", - key="command-key", - createdAt=datetime.now(), - status=cmd.CommandStatus.RUNNING, - ), - error=DefinedErrorData( - public=OverpressureError( - id="error-id", - createdAt=datetime.now(), - errorInfo={"retryLocation": (0, 0, 0)}, - ), - private=OverpressureErrorInternalData( - position=DeckPoint(x=0, y=0, z=0) - ), - ), - command_id="command-id", - error_id="error-id", - failed_at=datetime.now(), - notes=[], - type=ErrorRecoveryType.WAIT_FOR_RECOVERY, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="dispense-labware-id", - well_name="dispense-well-name", - ), - ), # liquidProbe and tryLiquidProbe succeeding and with overpressure error ( SucceedCommandAction( @@ -685,19 +625,20 @@ def test_movement_commands_without_well_clear_current_well( pipette_name=PipetteNameType.P300_SINGLE, mount=MountType.LEFT, ) - move_command = create_move_to_well_command( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - ) - subject.handle_action( SucceedCommandAction(private_result=None, command=load_pipette_command) ) + subject.handle_action( - SucceedCommandAction(private_result=None, command=move_command) + _create_move_to_well_action( + pipette_id="pipette-id", + labware_id="labware-id", + well_name="well-name", + deck_point=DeckPoint(x=1, y=2, z=3), + ) ) - subject.handle_action(SucceedCommandAction(private_result=None, command=command)) + + subject.handle_action(SucceedCommandAction(command=command, private_result=None)) assert subject.state.current_location is None @@ -737,19 +678,19 @@ def test_heater_shaker_command_without_movement( pipette_name=PipetteNameType.P300_SINGLE, mount=MountType.LEFT, ) - move_command = create_move_to_well_command( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - destination=DeckPoint(x=1, y=2, z=3), - ) - subject.handle_action( SucceedCommandAction(private_result=None, command=load_pipette_command) ) + subject.handle_action( - SucceedCommandAction(private_result=None, command=move_command) + _create_move_to_well_action( + pipette_id="pipette-id", + labware_id="labware-id", + well_name="well-name", + deck_point=DeckPoint(x=1, y=2, z=3), + ) ) + subject.handle_action(SucceedCommandAction(private_result=None, command=command)) assert subject.state.current_location == CurrentWell( @@ -849,17 +790,17 @@ def test_move_labware_clears_current_well( pipette_name=PipetteNameType.P300_SINGLE, mount=MountType.LEFT, ) - move_to_well_command = create_move_to_well_command( - pipette_id="pipette-id", - labware_id="matching-labware-id", - well_name="well-name", - ) - subject.handle_action( SucceedCommandAction(private_result=None, command=load_pipette_command) ) + subject.handle_action( - SucceedCommandAction(private_result=None, command=move_to_well_command) + _create_move_to_well_action( + pipette_id="pipette-id", + labware_id="matching-labware-id", + well_name="well-name", + deck_point=DeckPoint(x=1, y=2, z=3), + ) ) subject.handle_action( @@ -956,87 +897,6 @@ def test_add_pipette_config( @pytest.mark.parametrize( "action", ( - SucceedCommandAction( - command=create_aspirate_command( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - volume=1337, - flow_rate=1.23, - destination=DeckPoint(x=11, y=22, z=33), - ), - private_result=None, - ), - FailCommandAction( - running_command=cmd.Aspirate( - params=cmd.AspirateParams( - pipetteId="pipette-id", - labwareId="labware-id", - wellName="well-name", - volume=99999, - flowRate=1.23, - ), - id="command-id", - key="command-key", - createdAt=datetime.now(), - status=cmd.CommandStatus.RUNNING, - ), - error=DefinedErrorData( - public=OverpressureError( - id="error-id", - detail="error-detail", - createdAt=datetime.now(), - errorInfo={"retryLocation": (11, 22, 33)}, - ), - private=OverpressureErrorInternalData( - position=DeckPoint(x=11, y=22, z=33) - ), - ), - command_id="command-id", - error_id="error-id", - failed_at=datetime.now(), - notes=[], - type=ErrorRecoveryType.WAIT_FOR_RECOVERY, - ), - SucceedCommandAction( - command=create_dispense_command( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - volume=1337, - flow_rate=1.23, - destination=DeckPoint(x=11, y=22, z=33), - ), - private_result=None, - ), - SucceedCommandAction( - command=create_blow_out_command( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - flow_rate=1.23, - destination=DeckPoint(x=11, y=22, z=33), - ), - private_result=None, - ), - SucceedCommandAction( - command=create_pick_up_tip_command( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - destination=DeckPoint(x=11, y=22, z=33), - ), - private_result=None, - ), - SucceedCommandAction( - command=create_drop_tip_command( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - destination=DeckPoint(x=11, y=22, z=33), - ), - private_result=None, - ), SucceedCommandAction( command=create_touch_tip_command( pipette_id="pipette-id", @@ -1046,15 +906,6 @@ def test_add_pipette_config( ), private_result=None, ), - SucceedCommandAction( - command=create_move_to_well_command( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - destination=DeckPoint(x=11, y=22, z=33), - ), - private_result=None, - ), SucceedCommandAction( command=create_move_to_coordinates_command( pipette_id="pipette-id", @@ -1069,95 +920,6 @@ def test_add_pipette_config( ), private_result=None, ), - FailCommandAction( - running_command=cmd.Dispense( - params=cmd.DispenseParams( - pipetteId="pipette-id", - labwareId="labware-id", - wellName="well-name", - volume=125, - flowRate=1.23, - ), - id="command-id", - key="command-key", - createdAt=datetime.now(), - status=cmd.CommandStatus.RUNNING, - ), - error=DefinedErrorData( - public=OverpressureError( - id="error-id", - detail="error-detail", - createdAt=datetime.now(), - errorInfo={"retryLocation": (11, 22, 33)}, - ), - private=OverpressureErrorInternalData( - position=DeckPoint(x=11, y=22, z=33) - ), - ), - command_id="command-id", - error_id="error-id", - failed_at=datetime.now(), - notes=[], - type=ErrorRecoveryType.WAIT_FOR_RECOVERY, - ), - FailCommandAction( - running_command=cmd.AspirateInPlace( - params=cmd.AspirateInPlaceParams( - pipetteId="pipette-id", - volume=125, - flowRate=1.23, - ), - id="command-id", - key="command-key", - createdAt=datetime.now(), - status=cmd.CommandStatus.RUNNING, - ), - error=DefinedErrorData( - public=OverpressureError( - id="error-id", - detail="error-detail", - createdAt=datetime.now(), - errorInfo={"retryLocation": (11, 22, 33)}, - ), - private=OverpressureErrorInternalData( - position=DeckPoint(x=11, y=22, z=33) - ), - ), - command_id="command-id", - error_id="error-id", - failed_at=datetime.now(), - notes=[], - type=ErrorRecoveryType.WAIT_FOR_RECOVERY, - ), - FailCommandAction( - running_command=cmd.DispenseInPlace( - params=cmd.DispenseInPlaceParams( - pipetteId="pipette-id", - volume=125, - flowRate=1.23, - ), - id="command-id", - key="command-key", - createdAt=datetime.now(), - status=cmd.CommandStatus.RUNNING, - ), - error=DefinedErrorData( - public=OverpressureError( - id="error-id", - detail="error-detail", - createdAt=datetime.now(), - errorInfo={"retryLocation": (11, 22, 33)}, - ), - private=OverpressureErrorInternalData( - position=DeckPoint(x=11, y=22, z=33) - ), - ), - command_id="command-id", - error_id="error-id", - failed_at=datetime.now(), - notes=[], - type=ErrorRecoveryType.WAIT_FOR_RECOVERY, - ), ), ) def test_movement_commands_update_deck_point( @@ -1239,32 +1001,28 @@ def test_homing_commands_clear_deck_point( command: cmd.Command, subject: PipetteStore, ) -> None: - """It should save the last used pipette, labware, and well for movement commands.""" + """Commands that homed the robot should clear the deck point.""" load_pipette_command = create_load_pipette_command( pipette_id="pipette-id", pipette_name=PipetteNameType.P300_SINGLE, mount=MountType.LEFT, ) - move_command = create_move_to_well_command( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - destination=DeckPoint(x=1, y=2, z=3), - ) - subject.handle_action( SucceedCommandAction(private_result=None, command=load_pipette_command) ) subject.handle_action( - SucceedCommandAction(private_result=None, command=move_command) + _create_move_to_well_action( + pipette_id="pipette-id", + labware_id="labware-id", + well_name="well-name", + deck_point=DeckPoint(x=1, y=2, z=3), + ) ) - assert subject.state.current_deck_point == CurrentDeckPoint( mount=MountType.LEFT, deck_point=DeckPoint(x=1, y=2, z=3) ) subject.handle_action(SucceedCommandAction(private_result=None, command=command)) - assert subject.state.current_deck_point == CurrentDeckPoint( mount=None, deck_point=None ) From 4ddd1cff491c95800c67a25afc577809205f1019 Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Mon, 16 Sep 2024 11:19:23 -0400 Subject: [PATCH 02/24] feat(app): Add command text for set rail lights (#16255) Fix an issue that's been around for a while where we just didn't have a command text for setting rail lights. Closes RQA-2858 --- .../en/protocol_command_text.json | 2 ++ .../hooks/useCommandTextString/index.tsx | 5 ++++ .../utils/getRailLightsCommandText.ts | 15 ++++++++++++ .../hooks/useCommandTextString/utils/index.ts | 1 + shared-data/command/types/incidental.ts | 23 +++++++++++++++++-- 5 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 app/src/molecules/Command/hooks/useCommandTextString/utils/getRailLightsCommandText.ts diff --git a/app/src/assets/localization/en/protocol_command_text.json b/app/src/assets/localization/en/protocol_command_text.json index b050ff7bc7a..379a047cda8 100644 --- a/app/src/assets/localization/en/protocol_command_text.json +++ b/app/src/assets/localization/en/protocol_command_text.json @@ -66,6 +66,8 @@ "setting_temperature_module_temp": "Setting Temperature Module to {{temp}} (rounded to nearest integer)", "setting_thermocycler_block_temp": "Setting Thermocycler block temperature to {{temp}} with hold time of {{hold_time_seconds}} seconds after target reached", "setting_thermocycler_lid_temp": "Setting Thermocycler lid temperature to {{temp}}", + "turning_rail_lights_off": "Turning rail lights off", + "turning_rail_lights_on": "Turning rail lights on", "slot": "Slot {{slot_name}}", "target_temperature": "target temperature", "tc_awaiting_for_duration": "Waiting for Thermocycler profile to complete", diff --git a/app/src/molecules/Command/hooks/useCommandTextString/index.tsx b/app/src/molecules/Command/hooks/useCommandTextString/index.tsx index 05552f062f9..d10a9aa3211 100644 --- a/app/src/molecules/Command/hooks/useCommandTextString/index.tsx +++ b/app/src/molecules/Command/hooks/useCommandTextString/index.tsx @@ -217,6 +217,11 @@ export function useCommandTextString( commandText: utils.getCustomCommandText({ ...fullParams, command }), } + case 'setRailLights': + return { + commandText: utils.getRailLightsCommandText({ ...fullParams, command }), + } + case undefined: case null: return { commandText: '' } diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getRailLightsCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getRailLightsCommandText.ts new file mode 100644 index 00000000000..b731d3ec392 --- /dev/null +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/getRailLightsCommandText.ts @@ -0,0 +1,15 @@ +import type { RunTimeCommand } from '@opentrons/shared-data/command' +import type { HandlesCommands } from './types' + +type HandledCommands = Extract + +export type GetRailLightsCommandText = HandlesCommands + +export function getRailLightsCommandText({ + command, + t, +}: GetRailLightsCommandText): string { + return command.params.on + ? t('turning_rail_lights_on') + : t('turning_rail_lights_off') +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/index.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/index.ts index f7946ff1e47..ff3ad43fc8c 100644 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/index.ts +++ b/app/src/molecules/Command/hooks/useCommandTextString/utils/index.ts @@ -21,3 +21,4 @@ export { getCustomCommandText } from './getCustomCommandText' export { getUnknownCommandText } from './getUnknownCommandText' export { getPipettingCommandText } from './getPipettingCommandText' export { getLiquidProbeCommandText } from './getLiquidProbeCommandText' +export { getRailLightsCommandText } from './getRailLightsCommandText' diff --git a/shared-data/command/types/incidental.ts b/shared-data/command/types/incidental.ts index 9c67ea05106..bb754dcbee5 100644 --- a/shared-data/command/types/incidental.ts +++ b/shared-data/command/types/incidental.ts @@ -1,9 +1,13 @@ import type { CommonCommandRunTimeInfo, CommonCommandCreateInfo } from '.' import type { StatusBarAnimation } from '../../js/types' -export type IncidentalCreateCommand = SetStatusBarCreateCommand +export type IncidentalCreateCommand = + | SetStatusBarCreateCommand + | SetRailLightsCreateCommand -export type IncidentalRunTimeCommand = SetStatusBarRunTimeCommand +export type IncidentalRunTimeCommand = + | SetStatusBarRunTimeCommand + | SetRailLightsRunTimeCommand export interface SetStatusBarCreateCommand extends CommonCommandCreateInfo { commandType: 'setStatusBar' @@ -19,3 +23,18 @@ export interface SetStatusBarRunTimeCommand interface SetStatusBarParams { animation: StatusBarAnimation } + +export interface SetRailLightsCreateCommand extends CommonCommandCreateInfo { + commandType: 'setRailLights' + params: SetRailLightsParams +} + +export interface SetRailLightsRunTimeCommand + extends CommonCommandRunTimeInfo, + SetRailLightsCreateCommand { + result?: any +} + +interface SetRailLightsParams { + on: boolean +} From 6322438f3f9ddd74c5de7fcd5dc3d06f8c99ca24 Mon Sep 17 00:00:00 2001 From: Jethary Alcid <66035149+jerader@users.noreply.github.com> Date: Mon, 16 Sep 2024 15:37:50 -0400 Subject: [PATCH 03/24] refactor(protocol-designer): remove h-s adapter/labware combos (#16243) closes AUTH-776 --- .../src/pages/Designer/DeckSetup/LabwareTools.tsx | 9 ++++++--- .../src/pages/Designer/DeckSetup/constants.ts | 4 +++- .../src/utils/labwareModuleCompatibility.ts | 4 +++- shared-data/js/getLabware.ts | 9 ++++++--- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/protocol-designer/src/pages/Designer/DeckSetup/LabwareTools.tsx b/protocol-designer/src/pages/Designer/DeckSetup/LabwareTools.tsx index 5e37b4e0953..c1dc9c2e33f 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/LabwareTools.tsx +++ b/protocol-designer/src/pages/Designer/DeckSetup/LabwareTools.tsx @@ -20,6 +20,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' import { + ABSORBANCE_READER_TYPE, HEATERSHAKER_MODULE_TYPE, MAX_LABWARE_HEIGHT_EAST_WEST_HEATER_SHAKER_MM, OT2_ROBOT_TYPE, @@ -60,6 +61,8 @@ import type { LabwareDefByDefURI } from '../../../labware-defs' const CUSTOM_CATEGORY = 'custom' const STANDARD_X_DIMENSION = 127.75 const STANDARD_Y_DIMENSION = 85.48 +const PLATE_READER_LOADNAME = + 'opentrons_flex_lid_absorbance_plate_reader_module' interface LabwareToolsProps { slot: DeckSlotId setHoveredLabware: (defUri: string | null) => void @@ -128,10 +131,8 @@ export function LabwareTools(props: LabwareToolsProps): JSX.Element { const isSmallXDimension = xDimension < STANDARD_X_DIMENSION const isSmallYDimension = yDimension < STANDARD_Y_DIMENSION const isIrregularSize = isSmallXDimension && isSmallYDimension - const isAdapter = labwareDef.allowedRoles?.includes('adapter') const isAdapter96Channel = parameters.loadName === ADAPTER_96_CHANNEL - return ( (filterRecommended && !getLabwareIsRecommended(labwareDef, selectedModuleModel)) || @@ -145,7 +146,9 @@ export function LabwareTools(props: LabwareToolsProps): JSX.Element { isIrregularSize && moduleType !== HEATERSHAKER_MODULE_TYPE) || (isAdapter96Channel && !has96Channel) || - (slot === 'offDeck' && isAdapter) + (slot === 'offDeck' && isAdapter) || + (PLATE_READER_LOADNAME === parameters.loadName && + moduleType !== ABSORBANCE_READER_TYPE) ) }, [filterRecommended, filterHeight, getLabwareCompatible, moduleType, slot] diff --git a/protocol-designer/src/pages/Designer/DeckSetup/constants.ts b/protocol-designer/src/pages/Designer/DeckSetup/constants.ts index 7ce6f66b5c5..53571367f8b 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/constants.ts +++ b/protocol-designer/src/pages/Designer/DeckSetup/constants.ts @@ -90,7 +90,9 @@ export const RECOMMENDED_LABWARE_BY_MODULE: { [K in ModuleType]: string[] } = { 'nest_96_wellplate_2ml_deep', 'opentrons_96_wellplate_200ul_pcr_full_skirt', ], - [ABSORBANCE_READER_TYPE]: [], + [ABSORBANCE_READER_TYPE]: [ + 'opentrons_flex_lid_absorbance_plate_reader_module', + ], } export const MOAM_MODELS_WITH_FF: ModuleModel[] = [TEMPERATURE_MODULE_V2] diff --git a/protocol-designer/src/utils/labwareModuleCompatibility.ts b/protocol-designer/src/utils/labwareModuleCompatibility.ts index 30b62883ec9..52fd34ec9ed 100644 --- a/protocol-designer/src/utils/labwareModuleCompatibility.ts +++ b/protocol-designer/src/utils/labwareModuleCompatibility.ts @@ -67,7 +67,9 @@ export const COMPATIBLE_LABWARE_ALLOWLIST_BY_MODULE_TYPE: Record< 'armadillo_96_wellplate_200ul_pcr_full_skirt', 'biorad_96_wellplate_200ul_pcr', ], - [ABSORBANCE_READER_TYPE]: [], + [ABSORBANCE_READER_TYPE]: [ + 'opentrons_flex_lid_absorbance_plate_reader_module', + ], } export const getLabwareIsCompatible = ( def: LabwareDefinition2, diff --git a/shared-data/js/getLabware.ts b/shared-data/js/getLabware.ts index 792263fb938..be1daebb7ea 100644 --- a/shared-data/js/getLabware.ts +++ b/shared-data/js/getLabware.ts @@ -46,14 +46,17 @@ export const LABWAREV2_DO_NOT_LIST = [ 'opentrons_flex_lid_absorbance_plate_reader_module', ] // NOTE(sa, 2020-7-14): in PD we do not want to list calibration blocks -// but we still might want the rest of the labware in LABWAREV2_DO_NOT_LIST -// because of legacy protocols that might use them +// or the adapter/labware combos since we migrated to splitting them up export const PD_DO_NOT_LIST = [ 'opentrons_calibrationblock_short_side_left', 'opentrons_calibrationblock_short_side_right', 'opentrons_96_aluminumblock_biorad_wellplate_200ul', 'opentrons_96_aluminumblock_nest_wellplate_100ul', - 'opentrons_flex_lid_absorbance_plate_reader_module', + 'opentrons_universal_flat_adapter_corning_384_wellplate_112ul_flat', + 'opentrons_96_pcr_adapter_nest_wellplate_100ul_pcr_full_skirt', + 'opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat', + 'opentrons_96_deep_well_adapter_nest_wellplate_2ml_deep', + 'opentrons_96_pcr_adapter_armadillo_wellplate_200ul', ] export function getIsLabwareV1Tiprack(def: LabwareDefinition1): boolean { From cf93273895ec273e0184b73b8975f64af4f087e6 Mon Sep 17 00:00:00 2001 From: Shlok Amin Date: Mon, 16 Sep 2024 15:47:20 -0400 Subject: [PATCH 04/24] refactor(protocol-designer): remove basename from router (#16258) --- protocol-designer/src/ProtocolEditor.tsx | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/protocol-designer/src/ProtocolEditor.tsx b/protocol-designer/src/ProtocolEditor.tsx index 0df50b5a187..52d33b6ea77 100644 --- a/protocol-designer/src/ProtocolEditor.tsx +++ b/protocol-designer/src/ProtocolEditor.tsx @@ -29,34 +29,22 @@ import { Bouncing } from './Bouncing' import styles from './components/ProtocolEditor.module.css' import './css/reset.module.css' -import type { BrowserRouterProps } from 'react-router-dom' - const showGateModal = process.env.NODE_ENV === 'production' || process.env.OT_PD_SHOW_GATE -// sandbox urls get deployed to subdirectories in sandbox.designer.opentrons.com/${subFolder} -// prod urls get deployed to designer.opentrons.com with no subdir, so we don't need to add a base name -const routerBaseName = - process.env.NODE_ENV === 'production' - ? null - : `/${window.location.pathname.split('/')[1]}` - function ProtocolEditorComponent(): JSX.Element { const flags = useSelector(getFeatureFlagData) const enableRedesign = useSelector(getEnableRedesign) const prereleaseModeEnabled = flags.PRERELEASE_MODE === true - const browserRouterProps: BrowserRouterProps = - routerBaseName != null ? { basename: routerBaseName } : {} - return (
{enableRedesign ? ( {prereleaseModeEnabled ? : null} - + From 0cfab82d295dc09d12dcf389c7522b9f57ababa1 Mon Sep 17 00:00:00 2001 From: Shlok Amin Date: Mon, 16 Sep 2024 15:49:06 -0400 Subject: [PATCH 05/24] fix(labware-library): increase click area of LC stacking offset field (#16260) closes RQA-3173 --- .../src/labware-creator/components/sections/StackingOffsets.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/labware-library/src/labware-creator/components/sections/StackingOffsets.tsx b/labware-library/src/labware-creator/components/sections/StackingOffsets.tsx index ee5c6097658..58b3b947f27 100644 --- a/labware-library/src/labware-creator/components/sections/StackingOffsets.tsx +++ b/labware-library/src/labware-creator/components/sections/StackingOffsets.tsx @@ -248,7 +248,6 @@ export function StackingOffsets(): JSX.Element | null { {isChecked ? (
Date: Mon, 16 Sep 2024 16:15:32 -0400 Subject: [PATCH 06/24] fix(app): includeFixitCommands in GET run commands (#16253) # Overview closes [EXEC-704](https://opentrons.atlassian.net/browse/EXEC-704). revert do not render fixit commands and add query string to req. ## Test Plan and Hands on Testing - load an ER protocol. - enter ER. - dispatch a few fixit commands. - finish run. - make sure there are no fixit commands in the run preview. ## Changelog revert commend intent logic from UI. add `includeFixitCommands` in query string to GET run commands. ## Review requests am I missing anyhting? ## Risk assessment low. no change in appearance. [EXEC-704]: https://opentrons.atlassian.net/browse/EXEC-704?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --------- Co-authored-by: Jamey Huffnagle --- api-client/src/runs/commands/getCommands.ts | 4 +-- .../getCommandsAsPreSerializedList.ts | 4 +-- api-client/src/runs/commands/types.ts | 8 ++++++ .../Devices/hooks/useDownloadRunLog.ts | 2 ++ .../getPipettesWithTipAttached.ts | 2 ++ .../hooks/useCurrentRunCommands.ts | 4 +-- .../ProtocolUpload/hooks/useRunCommands.ts | 4 +-- app/src/organisms/RunPreview/index.tsx | 11 ++------ .../__tests__/RunProgressMeter.test.tsx | 5 +++- .../__tests__/RunningProtocol.test.tsx | 5 +++- ...useNotifyAllCommandsAsPreSerializedList.ts | 4 +-- .../runs/useNotifyAllCommandsQuery.ts | 28 +++++++++++++++++-- .../runs/useAllCommandsAsPreSerializedList.ts | 14 +++++++--- .../src/runs/useAllCommandsQuery.ts | 24 ++++++++++++---- 14 files changed, 86 insertions(+), 33 deletions(-) diff --git a/api-client/src/runs/commands/getCommands.ts b/api-client/src/runs/commands/getCommands.ts index 4833b94e5a8..95283c81b64 100644 --- a/api-client/src/runs/commands/getCommands.ts +++ b/api-client/src/runs/commands/getCommands.ts @@ -3,12 +3,12 @@ import { GET, request } from '../../request' import type { ResponsePromise } from '../../request' import type { HostConfig } from '../../types' import type { CommandsData } from '..' -import type { GetCommandsParams } from './types' +import type { GetRunCommandsParamsRequest } from './types' export function getCommands( config: HostConfig, runId: string, - params: GetCommandsParams + params: GetRunCommandsParamsRequest ): ResponsePromise { return request( GET, diff --git a/api-client/src/runs/commands/getCommandsAsPreSerializedList.ts b/api-client/src/runs/commands/getCommandsAsPreSerializedList.ts index 420f984b280..1d96f3d2209 100644 --- a/api-client/src/runs/commands/getCommandsAsPreSerializedList.ts +++ b/api-client/src/runs/commands/getCommandsAsPreSerializedList.ts @@ -4,13 +4,13 @@ import type { ResponsePromise } from '../../request' import type { HostConfig } from '../../types' import type { CommandsAsPreSerializedListData, - GetCommandsParams, + GetRunCommandsParamsRequest, } from './types' export function getCommandsAsPreSerializedList( config: HostConfig, runId: string, - params: GetCommandsParams + params: GetRunCommandsParamsRequest ): ResponsePromise { return request( GET, diff --git a/api-client/src/runs/commands/types.ts b/api-client/src/runs/commands/types.ts index cd18924201c..8c5517a1fe3 100644 --- a/api-client/src/runs/commands/types.ts +++ b/api-client/src/runs/commands/types.ts @@ -5,6 +5,14 @@ export interface GetCommandsParams { pageLength: number // the number of items to include } +export interface GetRunCommandsParams extends GetCommandsParams { + includeFixitCommands?: boolean +} + +export interface GetRunCommandsParamsRequest extends GetCommandsParams { + includeFixitCommands: boolean | null +} + export interface RunCommandErrors { data: RunCommandError[] meta: GetCommandsParams & { totalLength: number } diff --git a/app/src/organisms/Devices/hooks/useDownloadRunLog.ts b/app/src/organisms/Devices/hooks/useDownloadRunLog.ts index 1652efc4442..777f50bb806 100644 --- a/app/src/organisms/Devices/hooks/useDownloadRunLog.ts +++ b/app/src/organisms/Devices/hooks/useDownloadRunLog.ts @@ -29,12 +29,14 @@ export function useDownloadRunLog( getCommands(host, runId, { cursor: null, pageLength: 0, + includeFixitCommands: true, }) .then(response => { const { totalLength } = response.data.meta getCommands(host, runId, { cursor: 0, pageLength: totalLength, + includeFixitCommands: true, }) .then(response => { const commands = response.data diff --git a/app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/getPipettesWithTipAttached.ts b/app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/getPipettesWithTipAttached.ts index 99bcd949093..42b006ca0b2 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/getPipettesWithTipAttached.ts +++ b/app/src/organisms/DropTipWizardFlows/hooks/useTipAttachmentStatus/getPipettesWithTipAttached.ts @@ -50,11 +50,13 @@ function getCommandsExecutedDuringRun( return getCommands(host, runId, { cursor: null, pageLength: 0, + includeFixitCommands: true, }).then(response => { const { totalLength } = response.data.meta return getCommands(host, runId, { cursor: 0, pageLength: totalLength, + includeFixitCommands: null, }).then(response => response.data) }) } diff --git a/app/src/organisms/ProtocolUpload/hooks/useCurrentRunCommands.ts b/app/src/organisms/ProtocolUpload/hooks/useCurrentRunCommands.ts index b6cc00709f9..543d90cb899 100644 --- a/app/src/organisms/ProtocolUpload/hooks/useCurrentRunCommands.ts +++ b/app/src/organisms/ProtocolUpload/hooks/useCurrentRunCommands.ts @@ -4,11 +4,11 @@ import type { UseQueryOptions } from 'react-query' import type { CommandsData, RunCommandSummary, - GetCommandsParams, + GetRunCommandsParams, } from '@opentrons/api-client' export function useCurrentRunCommands( - params?: GetCommandsParams, + params?: GetRunCommandsParams, options?: UseQueryOptions ): RunCommandSummary[] | null { const currentRunId = useCurrentRunId() diff --git a/app/src/organisms/ProtocolUpload/hooks/useRunCommands.ts b/app/src/organisms/ProtocolUpload/hooks/useRunCommands.ts index 394c8a3eac0..cb3e70296f8 100644 --- a/app/src/organisms/ProtocolUpload/hooks/useRunCommands.ts +++ b/app/src/organisms/ProtocolUpload/hooks/useRunCommands.ts @@ -4,14 +4,14 @@ import type { UseQueryOptions } from 'react-query' import type { CommandsData, RunCommandSummary, - GetCommandsParams, + GetRunCommandsParams, } from '@opentrons/api-client' const REFETCH_INTERVAL = 3000 export function useRunCommands( runId: string | null, - params?: GetCommandsParams, + params?: GetRunCommandsParams, options?: UseQueryOptions ): RunCommandSummary[] | null { const { data: commandsData } = useNotifyAllCommandsQuery(runId, params, { diff --git a/app/src/organisms/RunPreview/index.tsx b/app/src/organisms/RunPreview/index.tsx index 8efdc6dce3e..27ed73fedf9 100644 --- a/app/src/organisms/RunPreview/index.tsx +++ b/app/src/organisms/RunPreview/index.tsx @@ -64,7 +64,7 @@ export const RunPreviewComponent = ( isLoading: isRunCommandDataLoading, } = useNotifyAllCommandsAsPreSerializedList( runId, - { cursor: 0, pageLength: MAX_COMMANDS }, + { cursor: 0, pageLength: MAX_COMMANDS, includeFixitCommands: false }, { enabled: isRunTerminal, } @@ -78,20 +78,13 @@ export const RunPreviewComponent = ( isCurrentCommandVisible, setIsCurrentCommandVisible, ] = React.useState(true) - const filteredCommandsFromQuery = React.useMemo( - () => - commandsFromQuery?.filter( - command => !('intent' in command) || command.intent !== 'fixit' - ), - [commandsFromQuery == null] - ) if (robotSideAnalysis == null) { return null } const commands = isRunTerminal - ? filteredCommandsFromQuery + ? commandsFromQuery : robotSideAnalysis.commands // pass relevant data from run rather than analysis so that CommandText utilities can properly hash the entities' IDs diff --git a/app/src/organisms/RunProgressMeter/__tests__/RunProgressMeter.test.tsx b/app/src/organisms/RunProgressMeter/__tests__/RunProgressMeter.test.tsx index 17bbdb279c0..f84087b4c3c 100644 --- a/app/src/organisms/RunProgressMeter/__tests__/RunProgressMeter.test.tsx +++ b/app/src/organisms/RunProgressMeter/__tests__/RunProgressMeter.test.tsx @@ -76,7 +76,10 @@ describe('RunProgressMeter', () => { .calledWith(NON_DETERMINISTIC_RUN_ID) .thenReturn(null) when(useNotifyAllCommandsQuery) - .calledWith(NON_DETERMINISTIC_RUN_ID, { cursor: null, pageLength: 1 }) + .calledWith(NON_DETERMINISTIC_RUN_ID, { + cursor: null, + pageLength: 1, + }) .thenReturn(mockUseAllCommandsResponseNonDeterministic) when(useCommandQuery) .calledWith(NON_DETERMINISTIC_RUN_ID, NON_DETERMINISTIC_COMMAND_KEY) diff --git a/app/src/pages/ODD/RunningProtocol/__tests__/RunningProtocol.test.tsx b/app/src/pages/ODD/RunningProtocol/__tests__/RunningProtocol.test.tsx index 5c74ad7e95e..866f33e34e7 100644 --- a/app/src/pages/ODD/RunningProtocol/__tests__/RunningProtocol.test.tsx +++ b/app/src/pages/ODD/RunningProtocol/__tests__/RunningProtocol.test.tsx @@ -151,7 +151,10 @@ describe('RunningProtocol', () => { .calledWith(RUN_ID) .thenReturn(mockRobotSideAnalysis) when(vi.mocked(useNotifyAllCommandsQuery)) - .calledWith(RUN_ID, { cursor: null, pageLength: 1 }) + .calledWith(RUN_ID, { + cursor: null, + pageLength: 1, + }) .thenReturn(mockUseAllCommandsResponseNonDeterministic) vi.mocked(useLastRunCommand).mockReturnValue({ key: 'FAKE_COMMAND_KEY', diff --git a/app/src/resources/runs/useNotifyAllCommandsAsPreSerializedList.ts b/app/src/resources/runs/useNotifyAllCommandsAsPreSerializedList.ts index 5811c051e54..5668b824667 100644 --- a/app/src/resources/runs/useNotifyAllCommandsAsPreSerializedList.ts +++ b/app/src/resources/runs/useNotifyAllCommandsAsPreSerializedList.ts @@ -4,12 +4,12 @@ import { useNotifyDataReady } from '../useNotifyDataReady' import type { UseQueryResult } from 'react-query' import type { AxiosError } from 'axios' -import type { CommandsData, GetCommandsParams } from '@opentrons/api-client' +import type { CommandsData, GetRunCommandsParams } from '@opentrons/api-client' import type { QueryOptionsWithPolling } from '../useNotifyDataReady' export function useNotifyAllCommandsAsPreSerializedList( runId: string | null, - params?: GetCommandsParams | null, + params?: GetRunCommandsParams | null, options: QueryOptionsWithPolling = {} ): UseQueryResult { const { shouldRefetch, queryOptionsNotify } = useNotifyDataReady({ diff --git a/app/src/resources/runs/useNotifyAllCommandsQuery.ts b/app/src/resources/runs/useNotifyAllCommandsQuery.ts index 4482ea972fd..12bafb21ef3 100644 --- a/app/src/resources/runs/useNotifyAllCommandsQuery.ts +++ b/app/src/resources/runs/useNotifyAllCommandsQuery.ts @@ -3,12 +3,23 @@ import { useAllCommandsQuery } from '@opentrons/react-api-client' import { useNotifyDataReady } from '../useNotifyDataReady' import type { UseQueryResult } from 'react-query' -import type { CommandsData, GetCommandsParams } from '@opentrons/api-client' +import type { + CommandsData, + GetRunCommandsParams, + GetCommandsParams, +} from '@opentrons/api-client' import type { QueryOptionsWithPolling } from '../useNotifyDataReady' +const DEFAULT_PAGE_LENGTH = 30 + +export const DEFAULT_PARAMS: GetCommandsParams = { + cursor: null, + pageLength: DEFAULT_PAGE_LENGTH, +} + export function useNotifyAllCommandsQuery( runId: string | null, - params?: GetCommandsParams | null, + params?: GetRunCommandsParams | null, options: QueryOptionsWithPolling = {} ): UseQueryResult { // Assume the useAllCommandsQuery() response can only change when the command links change. @@ -21,8 +32,19 @@ export function useNotifyAllCommandsQuery( topic: 'robot-server/runs/commands_links', options, }) + const nullCheckedParams = params ?? DEFAULT_PARAMS + + const nullCheckedFixitCommands = params?.includeFixitCommands ?? null + const finalizedNullCheckParams = { + ...nullCheckedParams, + includeFixitCommands: nullCheckedFixitCommands, + } - const httpQueryResult = useAllCommandsQuery(runId, params, queryOptionsNotify) + const httpQueryResult = useAllCommandsQuery( + runId, + finalizedNullCheckParams, + queryOptionsNotify + ) if (shouldRefetch) { void httpQueryResult.refetch() diff --git a/react-api-client/src/runs/useAllCommandsAsPreSerializedList.ts b/react-api-client/src/runs/useAllCommandsAsPreSerializedList.ts index 4d0a34295d0..4f7e2fdc0e0 100644 --- a/react-api-client/src/runs/useAllCommandsAsPreSerializedList.ts +++ b/react-api-client/src/runs/useAllCommandsAsPreSerializedList.ts @@ -7,21 +7,21 @@ import { useHost } from '../api' import type { UseQueryOptions, UseQueryResult } from 'react-query' import type { - GetCommandsParams, + GetRunCommandsParams, HostConfig, CommandsData, RunCommandSummary, } from '@opentrons/api-client' const DEFAULT_PAGE_LENGTH = 30 -export const DEFAULT_PARAMS: GetCommandsParams = { +export const DEFAULT_PARAMS: GetRunCommandsParams = { cursor: null, pageLength: DEFAULT_PAGE_LENGTH, } export function useAllCommandsAsPreSerializedList( runId: string | null, - params?: GetCommandsParams | null, + params?: GetRunCommandsParams | null, options: UseQueryOptions = {} ): UseQueryResult { const host = useHost() @@ -32,6 +32,11 @@ export function useAllCommandsAsPreSerializedList( enabled: host !== null && runId != null && options.enabled !== false, } const { cursor, pageLength } = nullCheckedParams + const nullCheckedFixitCommands = params?.includeFixitCommands ?? null + const finalizedNullCheckParams = { + ...nullCheckedParams, + includeFixitCommands: nullCheckedFixitCommands, + } // map undefined values to null to agree with react query caching // TODO (nd: 05/15/2024) create sanitizer for react query key objects @@ -45,12 +50,13 @@ export function useAllCommandsAsPreSerializedList( 'getCommandsAsPreSerializedList', cursor, pageLength, + nullCheckedFixitCommands, ], () => { return getCommandsAsPreSerializedList( host as HostConfig, runId as string, - nullCheckedParams + finalizedNullCheckParams ).then(response => { const responseData = response.data return { diff --git a/react-api-client/src/runs/useAllCommandsQuery.ts b/react-api-client/src/runs/useAllCommandsQuery.ts index 20c598d733f..427e96b554f 100644 --- a/react-api-client/src/runs/useAllCommandsQuery.ts +++ b/react-api-client/src/runs/useAllCommandsQuery.ts @@ -3,20 +3,21 @@ import { getCommands } from '@opentrons/api-client' import { useHost } from '../api' import type { UseQueryOptions, UseQueryResult } from 'react-query' import type { - GetCommandsParams, + GetRunCommandsParamsRequest, HostConfig, CommandsData, } from '@opentrons/api-client' const DEFAULT_PAGE_LENGTH = 30 -export const DEFAULT_PARAMS: GetCommandsParams = { +export const DEFAULT_PARAMS: GetRunCommandsParamsRequest = { cursor: null, pageLength: DEFAULT_PAGE_LENGTH, + includeFixitCommands: null, } export function useAllCommandsQuery( runId: string | null, - params?: GetCommandsParams | null, + params?: GetRunCommandsParamsRequest | null, options: UseQueryOptions = {} ): UseQueryResult { const host = useHost() @@ -27,13 +28,26 @@ export function useAllCommandsQuery( enabled: host !== null && runId != null && options.enabled !== false, } const { cursor, pageLength } = nullCheckedParams + const nullCheckedFixitCommands = params?.includeFixitCommands ?? null + const finalizedNullCheckParams = { + ...nullCheckedParams, + includeFixitCommands: nullCheckedFixitCommands, + } const query = useQuery( - [host, 'runs', runId, 'commands', cursor, pageLength], + [ + host, + 'runs', + runId, + 'commands', + cursor, + pageLength, + finalizedNullCheckParams, + ], () => { return getCommands( host as HostConfig, runId as string, - nullCheckedParams + finalizedNullCheckParams ).then(response => response.data) }, allOptions From 99efcc1963135acb9015a0983f60feb5d3e19ef9 Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Mon, 16 Sep 2024 17:44:26 -0400 Subject: [PATCH 07/24] refactor(app): consolidate ODD setup organisms (#16254) Step 2 of the process begun by consolidating app pages by device is to consolidate the organisms that are used only by one device into a consistent place. A good place to start with that was the organisms that make up the ODD prepare-to-run flow. This is because all these organisms were equally top level but heavily dependent on each other and on the page in which they live. The other thing that all of these organisms depend on is computation-focused hooks for analyzing command lists and pulling out things like labware-location pairs for LPC. These hooks all live in the component for which they were first written, and are then imported from other components without any eye toward hierarchy - so sometimes there are desktop components that import hooks or free functions from inside ODD organisms, sometimes ODD pages import hooks or free functions from desktop components. Instead, we can take a page from the book that brought us `app/src/resources` - a location for sharing hooks that wrap some robot API functionality in an app-specific way, and thus are too specific for `react-api-client` while too general to be in a specific component; a motivating example is the notify hooks - and add `app/src/transformations`. This is a place for hooks or free functions that do data transformation and analysis. A motivating example is pulling a list of labware-location pairs for displaying offsets, and in `transformations/commands` that's what you'll see. Now, - `app/src/organisms/OnDeviceDisplay`->`app/src/organisms/ODD` for consistency with the pages (7175bc3c536c1f22bb5361fbab5b16dcde085ce9) - All the ODD setup-to-run flows live in `organisms/ODD/ProtocolSetup`, effectively namespacing them from the desktop app equivalents (7175bc3c536c1f22bb5361fbab5b16dcde085ce9) - Since the ODD setup-to-run flows now have a directory in common, we can put some common definitions in there instead of in the page, removing all non-test `pages` imports except for a single specific screen (170b0caca6afebdf968ec19635c4baa311826a4f and caa574eec582033677fb381219dcfd1ecee64750) - Moving a bunch of things to `transformations/commands` (6c449b3a3875e45b67ee9e55aafbe942ddb39f9f) removes a bunch more weird imports ## Reviews and testing - There are no changes to application code other than import statements and the location of files in this PR - There are a couple mocking structure changes in tests - Keep an eye on all of it and give it a test build to see that it works --- .../ProtocolRunHeader/hooks/useRunErrors.ts | 2 +- .../SetupLabware/LabwareListItem.tsx | 2 +- .../SetupLabware/OffDeckLabwareList.tsx | 2 +- .../SetupLabware/SetupLabwareList.tsx | 4 +- .../SetupLabware/SetupLabwareMap.tsx | 2 +- .../__tests__/SetupLabwareMap.test.tsx | 4 +- .../__tests__/getNestedLabwareInfo.test.tsx | 2 +- .../SetupLabware/getNestedLabwareInfo.ts | 2 +- .../__tests__/SetupLiquidsMap.test.tsx | 6 +- .../SetupModuleAndDeck/SetupModulesMap.tsx | 2 +- .../__tests__/SetupModulesMap.test.tsx | 4 +- .../NameRobot/ConfirmRobotName.tsx | 0 .../__tests__/ConfirmRobotName.test.tsx | 0 .../ProtocolDetailsSkeleton.tsx | 0 .../ProtocolDetailsSkeleton.test.tsx | 0 .../ProtocolDetails/index.ts | 0 .../ProtocolSetupDeckConfiguration.test.tsx | 12 +- .../ProtocolSetupDeckConfiguration/index.tsx | 14 +- .../ProtocolSetupInstruments.tsx} | 14 +- .../__fixtures__/index.ts | 0 .../ProtocolSetupInstruments.test.tsx | 12 +- .../ProtocolSetupInstruments/index.ts | 2 + .../ProtocolSetupInstruments/utils.ts | 13 +- .../ProtocolSetupLabware/LabwareMapView.tsx | 4 +- .../SingleLabwareModal.tsx | 4 +- .../__fixtures__/index.ts | 0 .../__tests__/LabwareMapView.test.tsx | 14 +- .../__tests__/ProtocolSetupLabware.test.tsx | 16 +- .../ProtocolSetupLabware/index.tsx | 22 +- .../ProtocolSetupLiquids/LiquidDetails.tsx | 6 +- .../__tests__/LiquidDetails.test.tsx | 20 +- .../__tests__/ProtocolSetupLiquids.test.tsx | 16 +- .../ProtocolSetupLiquids/fixtures.ts | 0 .../ProtocolSetupLiquids/index.tsx | 10 +- .../FixtureTable.tsx | 14 +- .../ModuleTable.tsx | 24 +- .../ModulesAndDeckMapView.tsx | 4 +- .../ProtocolSetupModulesAndDeck.tsx} | 20 +- .../SetupInstructionsModal.tsx | 6 +- .../__tests__/FixtureTable.test.tsx | 18 +- .../__tests__/ModulesAndDeckMapView.test.tsx | 14 +- .../ProtocolSetupModulesAndDeck.test.tsx | 50 +-- .../__tests__/SetupInstructionsModal.test.tsx | 4 +- .../__tests__/utils.test.tsx | 2 +- .../ProtocolSetupModulesAndDeck/index.ts | 2 + .../ProtocolSetupModulesAndDeck/utils.ts | 4 +- .../ProtocolSetupOffsets/index.tsx | 18 +- .../AnalysisFailed.stories.tsx | 2 +- .../AnalysisFailedModal.tsx | 6 +- .../ProtocolSetupParameters/ChooseCsvFile.tsx | 4 +- .../ProtocolSetupParameters/ChooseEnum.tsx | 4 +- .../ProtocolSetupParameters/ChooseNumber.tsx | 6 +- .../EmptyFile.stories.tsx | 0 .../ProtocolSetupParameters/EmptyFile.tsx | 0 .../ProtocolSetupParameters.tsx} | 10 +- .../ResetValuesModal.stories.tsx | 0 .../ResetValuesModal.tsx | 6 +- .../ViewOnlyParameters.tsx | 8 +- .../__tests__/AnalysisFailedModal.test.tsx | 4 +- .../__tests__/ChooseCsvFile.test.tsx | 14 +- .../__tests__/ChooseEnum.test.tsx | 6 +- .../__tests__/ChooseNumber.test.tsx | 10 +- .../__tests__/EmptyFile.test.tsx | 4 +- .../ProtocolSetupParameters.test.tsx | 16 +- .../__tests__/ResetValuesModal.test.tsx | 4 +- .../__tests__/ViewOnlyParameters.test.tsx | 14 +- .../ProtocolSetupParameters/index.ts | 3 + .../ProtocolSetup/ProtocolSetupSkeleton.tsx | 0 .../ProtocolSetup/ProtocolSetupStep/index.tsx | 194 ++++++++++ .../__tests__/ProtocolSetupSkeleton.test.tsx | 0 app/src/organisms/ODD/ProtocolSetup/index.ts | 11 + app/src/organisms/ODD/ProtocolSetup/types.ts | 9 + .../RobotDashboard/EmptyRecentRun.tsx | 0 .../RobotDashboard/RecentRunProtocolCard.tsx | 4 +- .../RecentRunProtocolCarousel.tsx | 0 .../RobotDashboard/ServerInitializing.tsx | 0 .../__tests__/EmptyRecentRun.test.tsx | 0 .../__tests__/RecentRunProtocolCard.test.tsx | 6 +- .../RecentRunProtocolCarousel.test.tsx | 0 .../__tests__/useHardwareStatusText.test.tsx | 0 .../RobotDashboard/hooks/index.ts | 0 .../hooks/useHardwareStatusText.ts | 2 +- .../hooks/useRerunnableStatusText.ts | 2 +- .../RobotDashboard/index.ts | 0 .../RunningProtocol/CancelingRunModal.tsx | 0 .../RunningProtocol/ConfirmCancelRunModal.tsx | 0 .../CurrentRunningProtocolCommand.tsx | 0 .../RunningProtocol/PlayPauseButton.tsx | 0 .../RunningProtocol/RunFailedModal.tsx | 0 .../RunningProtocolCommandList.tsx | 0 .../RunningProtocolSkeleton.tsx | 0 .../RunningProtocol/StopButton.tsx | 0 .../__tests__/CancelingRunModal.test.tsx | 0 .../__tests__/ConfirmCancelRunModal.test.tsx | 0 .../CurrentRunningProtocolCommand.test.tsx | 0 .../__tests__/RunFailedModal.test.tsx | 0 .../RunningProtocolCommandList.test.tsx | 0 .../RunningProtocolSkeleton.test.tsx | 0 .../RunningProtocol/index.ts | 0 .../OnDeviceDisplay/ProtocolSetup/index.ts | 1 - app/src/organisms/ProtocolDetails/index.tsx | 2 +- .../ProtocolsLanding/ProtocolCard.tsx | 2 +- .../Protocols/hooks/__tests__/hooks.test.tsx | 206 +---------- .../pages/Desktop/Protocols/hooks/index.ts | 316 +--------------- app/src/pages/ODD/NameRobot/index.tsx | 2 +- app/src/pages/ODD/ProtocolDetails/Deck.tsx | 4 +- .../pages/ODD/ProtocolDetails/Hardware.tsx | 2 +- .../__tests__/ProtocolDetails.test.tsx | 17 +- app/src/pages/ODD/ProtocolDetails/index.tsx | 12 +- .../__tests__/ProtocolSetup.test.tsx | 54 +-- app/src/pages/ODD/ProtocolSetup/index.tsx | 241 ++---------- .../ODD/QuickTransferDetails/Hardware.tsx | 2 +- .../__tests__/Hardware.test.tsx | 1 + .../__tests__/QuickTransferDetails.test.tsx | 10 +- .../pages/ODD/QuickTransferDetails/index.tsx | 6 +- .../__tests__/RobotDashboard.test.tsx | 12 +- app/src/pages/ODD/RobotDashboard/index.tsx | 4 +- app/src/pages/ODD/RunSummary/index.tsx | 2 +- .../__tests__/RunningProtocol.test.tsx | 10 +- app/src/pages/ODD/RunningProtocol/index.tsx | 6 +- .../useMissingProtocolHardware.test.tsx | 347 ++++++++++++++++++ .../transformations/commands/hooks/index.ts | 5 + .../transformations/commands/hooks/types.ts | 39 ++ .../hooks/useMissingProtocolHardware.ts | 36 ++ .../useMissingProtocolHardwareFromAnalysis.ts | 28 ++ ...colHardwareFromRequiredProtocolHardware.ts | 74 ++++ ...useRequiredProtocolHardwareFromAnalysis.ts | 165 +++++++++ app/src/transformations/commands/index.ts | 2 + .../getLabwareSetupItemGroups.ts} | 0 .../transformations/getProtocolUsesGripper.ts | 15 + .../commands/transformations/index.ts | 2 + 131 files changed, 1305 insertions(+), 1060 deletions(-) rename app/src/organisms/{OnDeviceDisplay => ODD}/NameRobot/ConfirmRobotName.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/NameRobot/__tests__/ConfirmRobotName.test.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/ProtocolDetails/ProtocolDetailsSkeleton.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/ProtocolDetails/__tests__/ProtocolDetailsSkeleton.test.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/ProtocolDetails/index.ts (100%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupDeckConfiguration/__tests__/ProtocolSetupDeckConfiguration.test.tsx (85%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupDeckConfiguration/index.tsx (88%) rename app/src/organisms/{ProtocolSetupInstruments/index.tsx => ODD/ProtocolSetup/ProtocolSetupInstruments/ProtocolSetupInstruments.tsx} (84%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupInstruments/__fixtures__/index.ts (100%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupInstruments/__tests__/ProtocolSetupInstruments.test.tsx (83%) create mode 100644 app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/index.ts rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupInstruments/utils.ts (88%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupLabware/LabwareMapView.tsx (94%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupLabware/SingleLabwareModal.tsx (96%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupLabware/__fixtures__/index.ts (100%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupLabware/__tests__/LabwareMapView.test.tsx (87%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupLabware/__tests__/ProtocolSetupLabware.test.tsx (88%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupLabware/index.tsx (95%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupLiquids/LiquidDetails.tsx (94%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupLiquids/__tests__/LiquidDetails.test.tsx (72%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupLiquids/__tests__/ProtocolSetupLiquids.test.tsx (79%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupLiquids/fixtures.ts (100%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupLiquids/index.tsx (93%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupModulesAndDeck/FixtureTable.tsx (92%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupModulesAndDeck/ModuleTable.tsx (90%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupModulesAndDeck/ModulesAndDeckMapView.tsx (89%) rename app/src/organisms/{ProtocolSetupModulesAndDeck/index.tsx => ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/ProtocolSetupModulesAndDeck.tsx} (88%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupModulesAndDeck/SetupInstructionsModal.tsx (88%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupModulesAndDeck/__tests__/FixtureTable.test.tsx (82%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupModulesAndDeck/__tests__/ModulesAndDeckMapView.test.tsx (89%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx (86%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupModulesAndDeck/__tests__/SetupInstructionsModal.test.tsx (92%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupModulesAndDeck/__tests__/utils.test.tsx (97%) create mode 100644 app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/index.ts rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupModulesAndDeck/utils.ts (96%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupOffsets/index.tsx (83%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupParameters/AnalysisFailed.stories.tsx (89%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupParameters/AnalysisFailedModal.tsx (92%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupParameters/ChooseCsvFile.tsx (97%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupParameters/ChooseEnum.tsx (95%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupParameters/ChooseNumber.tsx (96%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupParameters/EmptyFile.stories.tsx (100%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupParameters/EmptyFile.tsx (100%) rename app/src/organisms/{ProtocolSetupParameters/index.tsx => ODD/ProtocolSetup/ProtocolSetupParameters/ProtocolSetupParameters.tsx} (97%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupParameters/ResetValuesModal.stories.tsx (100%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupParameters/ResetValuesModal.tsx (91%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupParameters/ViewOnlyParameters.tsx (93%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupParameters/__tests__/AnalysisFailedModal.test.tsx (95%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupParameters/__tests__/ChooseCsvFile.test.tsx (92%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupParameters/__tests__/ChooseEnum.test.tsx (94%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupParameters/__tests__/ChooseNumber.test.tsx (92%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupParameters/__tests__/EmptyFile.test.tsx (92%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupParameters/__tests__/ProtocolSetupParameters.test.tsx (94%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupParameters/__tests__/ResetValuesModal.test.tsx (93%) rename app/src/organisms/{ => ODD/ProtocolSetup}/ProtocolSetupParameters/__tests__/ViewOnlyParameters.test.tsx (79%) create mode 100644 app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/index.ts rename app/src/organisms/{OnDeviceDisplay => ODD}/ProtocolSetup/ProtocolSetupSkeleton.tsx (100%) create mode 100644 app/src/organisms/ODD/ProtocolSetup/ProtocolSetupStep/index.tsx rename app/src/organisms/{OnDeviceDisplay => ODD}/ProtocolSetup/__tests__/ProtocolSetupSkeleton.test.tsx (100%) create mode 100644 app/src/organisms/ODD/ProtocolSetup/index.ts create mode 100644 app/src/organisms/ODD/ProtocolSetup/types.ts rename app/src/organisms/{OnDeviceDisplay => ODD}/RobotDashboard/EmptyRecentRun.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RobotDashboard/RecentRunProtocolCard.tsx (97%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RobotDashboard/RecentRunProtocolCarousel.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RobotDashboard/ServerInitializing.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RobotDashboard/__tests__/EmptyRecentRun.test.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RobotDashboard/__tests__/RecentRunProtocolCard.test.tsx (97%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RobotDashboard/__tests__/RecentRunProtocolCarousel.test.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RobotDashboard/hooks/__tests__/useHardwareStatusText.test.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RobotDashboard/hooks/index.ts (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RobotDashboard/hooks/useHardwareStatusText.ts (94%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RobotDashboard/hooks/useRerunnableStatusText.ts (85%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RobotDashboard/index.ts (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RunningProtocol/CancelingRunModal.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RunningProtocol/ConfirmCancelRunModal.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RunningProtocol/CurrentRunningProtocolCommand.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RunningProtocol/PlayPauseButton.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RunningProtocol/RunFailedModal.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RunningProtocol/RunningProtocolCommandList.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RunningProtocol/RunningProtocolSkeleton.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RunningProtocol/StopButton.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RunningProtocol/__tests__/CancelingRunModal.test.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RunningProtocol/__tests__/ConfirmCancelRunModal.test.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RunningProtocol/__tests__/CurrentRunningProtocolCommand.test.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RunningProtocol/__tests__/RunFailedModal.test.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RunningProtocol/__tests__/RunningProtocolCommandList.test.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RunningProtocol/__tests__/RunningProtocolSkeleton.test.tsx (100%) rename app/src/organisms/{OnDeviceDisplay => ODD}/RunningProtocol/index.ts (100%) delete mode 100644 app/src/organisms/OnDeviceDisplay/ProtocolSetup/index.ts create mode 100644 app/src/transformations/commands/hooks/__tests__/useMissingProtocolHardware.test.tsx create mode 100644 app/src/transformations/commands/hooks/index.ts create mode 100644 app/src/transformations/commands/hooks/types.ts create mode 100644 app/src/transformations/commands/hooks/useMissingProtocolHardware.ts create mode 100644 app/src/transformations/commands/hooks/useMissingProtocolHardwareFromAnalysis.ts create mode 100644 app/src/transformations/commands/hooks/useMissingProtocolHardwareFromRequiredProtocolHardware.ts create mode 100644 app/src/transformations/commands/hooks/useRequiredProtocolHardwareFromAnalysis.ts create mode 100644 app/src/transformations/commands/index.ts rename app/src/{pages/Desktop/Protocols/utils/index.ts => transformations/commands/transformations/getLabwareSetupItemGroups.ts} (100%) create mode 100644 app/src/transformations/commands/transformations/getProtocolUsesGripper.ts create mode 100644 app/src/transformations/commands/transformations/index.ts diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader/hooks/useRunErrors.ts b/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader/hooks/useRunErrors.ts index 5a84a993d0b..58d39855f44 100644 --- a/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader/hooks/useRunErrors.ts +++ b/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader/hooks/useRunErrors.ts @@ -2,7 +2,7 @@ import { useRunCommandErrors } from '@opentrons/react-api-client' import { isTerminalRunStatus } from '../utils' import { useMostRecentRunId } from '../../../../ProtocolUpload/hooks/useMostRecentRunId' -import { getHighestPriorityError } from '../../../../OnDeviceDisplay/RunningProtocol' +import { getHighestPriorityError } from '../../../../ODD/RunningProtocol' import type { RunStatus, Run } from '@opentrons/api-client' import type { RunCommandError } from '@opentrons/shared-data' diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx index 982020f291a..a42596b58e6 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx @@ -50,7 +50,7 @@ import type { LoadLabwareRunTimeCommand, } from '@opentrons/shared-data' import type { ModuleRenderInfoForProtocol } from '../../hooks' -import type { LabwareSetupItem } from '../../../../pages/Desktop/Protocols/utils' +import type { LabwareSetupItem } from '../../../../transformations/commands' import type { ModuleTypesThatRequireExtraAttention } from '../utils/getModuleTypesThatRequireExtraAttention' import type { NestedLabwareInfo } from './getNestedLabwareInfo' diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/OffDeckLabwareList.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLabware/OffDeckLabwareList.tsx index f06cae04cdd..848f0d3247c 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/OffDeckLabwareList.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupLabware/OffDeckLabwareList.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next' import { SPACING, TYPOGRAPHY, LegacyStyledText } from '@opentrons/components' import { LabwareListItem } from './LabwareListItem' import type { RunTimeCommand } from '@opentrons/shared-data' -import type { LabwareSetupItem } from '../../../../pages/Desktop/Protocols/utils' +import type { LabwareSetupItem } from '../../../../transformations/commands' interface OffDeckLabwareListProps { labwareItems: LabwareSetupItem[] diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareList.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareList.tsx index 4b5d08ff51e..229dd98281c 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareList.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareList.tsx @@ -7,14 +7,14 @@ import { StyledText, COLORS, } from '@opentrons/components' -import { getLabwareSetupItemGroups } from '../../../../pages/Desktop/Protocols/utils' +import { getLabwareSetupItemGroups } from '../../../../transformations/commands' import { LabwareListItem } from './LabwareListItem' import { getNestedLabwareInfo } from './getNestedLabwareInfo' import type { RunTimeCommand } from '@opentrons/shared-data' import type { ModuleRenderInfoForProtocol } from '../../hooks' import type { ModuleTypesThatRequireExtraAttention } from '../utils/getModuleTypesThatRequireExtraAttention' -import type { LabwareSetupItem } from '../../../../pages/Desktop/Protocols/utils' +import type { LabwareSetupItem } from '../../../../transformations/commands' interface SetupLabwareListProps { attachedModuleInfo: { [moduleId: string]: ModuleRenderInfoForProtocol } diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx index 4b6fae96e7f..cd611397850 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx @@ -16,7 +16,7 @@ import { THERMOCYCLER_MODULE_V1, } from '@opentrons/shared-data' -import { getLabwareSetupItemGroups } from '../../../../pages/Desktop/Protocols/utils' +import { getLabwareSetupItemGroups } from '../../../../transformations/commands' import { LabwareInfoOverlay } from '../LabwareInfoOverlay' import { getLabwareRenderInfo } from '../utils/getLabwareRenderInfo' import { getProtocolModulesInfo } from '../utils/getProtocolModulesInfo' diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabwareMap.test.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabwareMap.test.tsx index 2f305f90dab..bc68e89bcb1 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabwareMap.test.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabwareMap.test.tsx @@ -13,7 +13,7 @@ import { import { renderWithProviders } from '../../../../../__testing-utils__' import { i18n } from '../../../../../i18n' -import { getAttachedProtocolModuleMatches } from '../../../../ProtocolSetupModulesAndDeck/utils' +import { getAttachedProtocolModuleMatches } from '../../../../ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/utils' import { LabwareInfoOverlay } from '../../LabwareInfoOverlay' import { getLabwareRenderInfo } from '../../utils/getLabwareRenderInfo' import { SetupLabwareMap } from '../SetupLabwareMap' @@ -40,7 +40,7 @@ vi.mock('@opentrons/shared-data', async importOriginal => { } }) -vi.mock('../../../../ProtocolSetupModulesAndDeck/utils') +vi.mock('../../../../ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/utils') vi.mock('../../LabwareInfoOverlay') vi.mock('../../utils/getLabwareRenderInfo') vi.mock('../../utils/getModuleTypesThatRequireExtraAttention') diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/getNestedLabwareInfo.test.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/getNestedLabwareInfo.test.tsx index b34f583fe27..737a5baf1db 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/getNestedLabwareInfo.test.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/getNestedLabwareInfo.test.tsx @@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest' import { mockDefinition } from '../../../../../redux/custom-labware/__fixtures__' import { getNestedLabwareInfo } from '../getNestedLabwareInfo' import type { RunTimeCommand } from '@opentrons/shared-data' -import type { LabwareSetupItem } from '../../../../../pages/Desktop/Protocols/utils' +import type { LabwareSetupItem } from '../../../../../transformations/commands' const MOCK_LABWARE_ID = 'mockLabwareId' const MOCK_OTHER_LABWARE_ID = 'mockOtherLabwareId' diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/getNestedLabwareInfo.ts b/app/src/organisms/Devices/ProtocolRun/SetupLabware/getNestedLabwareInfo.ts index 136a774057e..c24e2b0aa3f 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/getNestedLabwareInfo.ts +++ b/app/src/organisms/Devices/ProtocolRun/SetupLabware/getNestedLabwareInfo.ts @@ -4,7 +4,7 @@ import type { LoadModuleRunTimeCommand, RunTimeCommand, } from '@opentrons/shared-data' -import type { LabwareSetupItem } from '../../../../pages/Desktop/Protocols/utils' +import type { LabwareSetupItem } from '../../../../transformations/commands' export interface NestedLabwareInfo { nestedLabwareDisplayName: string diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsMap.test.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsMap.test.tsx index 098902f046f..b1260de8c83 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsMap.test.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsMap.test.tsx @@ -25,9 +25,9 @@ import { useAttachedModules } from '../../../hooks' import { LabwareInfoOverlay } from '../../LabwareInfoOverlay' import { getLabwareRenderInfo } from '../../utils/getLabwareRenderInfo' import { getStandardDeckViewLayerBlockList } from '../../utils/getStandardDeckViewLayerBlockList' -import { getAttachedProtocolModuleMatches } from '../../../../ProtocolSetupModulesAndDeck/utils' +import { getAttachedProtocolModuleMatches } from '../../../../ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/utils' import { getProtocolModulesInfo } from '../../utils/getProtocolModulesInfo' -import { mockProtocolModuleInfo } from '../../../../ProtocolSetupLabware/__fixtures__' +import { mockProtocolModuleInfo } from '../../../../ODD/ProtocolSetup/ProtocolSetupLabware/__fixtures__' import { mockFetchModulesSuccessActionPayloadModules } from '../../../../../redux/modules/__fixtures__' import { SetupLiquidsMap } from '../SetupLiquidsMap' @@ -52,7 +52,7 @@ vi.mock('../../LabwareInfoOverlay') vi.mock('../../../hooks') vi.mock('../utils') vi.mock('../../utils/getLabwareRenderInfo') -vi.mock('../../../../ProtocolSetupModulesAndDeck/utils') +vi.mock('../../../../ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/utils') vi.mock('../../utils/getProtocolModulesInfo') vi.mock('../../../../../resources/deck_configuration/utils') vi.mock('@opentrons/shared-data', async importOriginal => { diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/SetupModulesMap.tsx b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/SetupModulesMap.tsx index 72687bc8324..272f19c2b3c 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/SetupModulesMap.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/SetupModulesMap.tsx @@ -14,7 +14,7 @@ import { } from '@opentrons/shared-data' import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { getAttachedProtocolModuleMatches } from '../../../ProtocolSetupModulesAndDeck/utils' +import { getAttachedProtocolModuleMatches } from '../../../ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/utils' import { ModuleInfo } from '../../ModuleInfo' import { useAttachedModules, useStoredProtocolAnalysis } from '../../hooks' import { getProtocolModulesInfo } from '../utils/getProtocolModulesInfo' diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesMap.test.tsx b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesMap.test.tsx index e08ff19f415..b6c4984e6c2 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesMap.test.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesMap.test.tsx @@ -15,7 +15,7 @@ import { mockMagneticModule as mockMagneticModuleFixture, } from '../../../../../redux/modules/__fixtures__/index' import { useMostRecentCompletedAnalysis } from '../../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { getAttachedProtocolModuleMatches } from '../../../../ProtocolSetupModulesAndDeck/utils' +import { getAttachedProtocolModuleMatches } from '../../../../ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/utils' import { ModuleInfo } from '../../../ModuleInfo' import { SetupModulesMap } from '../SetupModulesMap' @@ -44,7 +44,7 @@ vi.mock('@opentrons/shared-data', async importOriginal => { } }) vi.mock('../../../../LabwarePositionCheck/useMostRecentCompletedAnalysis') -vi.mock('../../../../ProtocolSetupModulesAndDeck/utils') +vi.mock('../../../../ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/utils') vi.mock('../../../ModuleInfo') vi.mock('../../../hooks') diff --git a/app/src/organisms/OnDeviceDisplay/NameRobot/ConfirmRobotName.tsx b/app/src/organisms/ODD/NameRobot/ConfirmRobotName.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/NameRobot/ConfirmRobotName.tsx rename to app/src/organisms/ODD/NameRobot/ConfirmRobotName.tsx diff --git a/app/src/organisms/OnDeviceDisplay/NameRobot/__tests__/ConfirmRobotName.test.tsx b/app/src/organisms/ODD/NameRobot/__tests__/ConfirmRobotName.test.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/NameRobot/__tests__/ConfirmRobotName.test.tsx rename to app/src/organisms/ODD/NameRobot/__tests__/ConfirmRobotName.test.tsx diff --git a/app/src/organisms/OnDeviceDisplay/ProtocolDetails/ProtocolDetailsSkeleton.tsx b/app/src/organisms/ODD/ProtocolDetails/ProtocolDetailsSkeleton.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/ProtocolDetails/ProtocolDetailsSkeleton.tsx rename to app/src/organisms/ODD/ProtocolDetails/ProtocolDetailsSkeleton.tsx diff --git a/app/src/organisms/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetailsSkeleton.test.tsx b/app/src/organisms/ODD/ProtocolDetails/__tests__/ProtocolDetailsSkeleton.test.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetailsSkeleton.test.tsx rename to app/src/organisms/ODD/ProtocolDetails/__tests__/ProtocolDetailsSkeleton.test.tsx diff --git a/app/src/organisms/OnDeviceDisplay/ProtocolDetails/index.ts b/app/src/organisms/ODD/ProtocolDetails/index.ts similarity index 100% rename from app/src/organisms/OnDeviceDisplay/ProtocolDetails/index.ts rename to app/src/organisms/ODD/ProtocolDetails/index.ts diff --git a/app/src/organisms/ProtocolSetupDeckConfiguration/__tests__/ProtocolSetupDeckConfiguration.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupDeckConfiguration/__tests__/ProtocolSetupDeckConfiguration.test.tsx similarity index 85% rename from app/src/organisms/ProtocolSetupDeckConfiguration/__tests__/ProtocolSetupDeckConfiguration.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupDeckConfiguration/__tests__/ProtocolSetupDeckConfiguration.test.tsx index ef226bbfc19..499ea4cf717 100644 --- a/app/src/organisms/ProtocolSetupDeckConfiguration/__tests__/ProtocolSetupDeckConfiguration.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupDeckConfiguration/__tests__/ProtocolSetupDeckConfiguration.test.tsx @@ -9,11 +9,11 @@ import { useUpdateDeckConfigurationMutation, } from '@opentrons/react-api-client' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { i18n } from '../../../../../i18n' +import { useMostRecentCompletedAnalysis } from '../../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' import { ProtocolSetupDeckConfiguration } from '..' -import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration' +import { useNotifyDeckConfigurationQuery } from '../../../../../resources/deck_configuration' import type { UseQueryResult } from 'react-query' import type { @@ -24,8 +24,8 @@ import type { Modules } from '@opentrons/api-client' vi.mock('@opentrons/components/src/hardware-sim/BaseDeck/index') vi.mock('@opentrons/react-api-client') -vi.mock('../../LabwarePositionCheck/useMostRecentCompletedAnalysis') -vi.mock('../../../resources/deck_configuration') +vi.mock('../../../../LabwarePositionCheck/useMostRecentCompletedAnalysis') +vi.mock('../../../../../resources/deck_configuration') const mockSetSetupScreen = vi.fn() const PROTOCOL_DETAILS = { diff --git a/app/src/organisms/ProtocolSetupDeckConfiguration/index.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupDeckConfiguration/index.tsx similarity index 88% rename from app/src/organisms/ProtocolSetupDeckConfiguration/index.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupDeckConfiguration/index.tsx index c02f194cd20..c18604f51a8 100644 --- a/app/src/organisms/ProtocolSetupDeckConfiguration/index.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupDeckConfiguration/index.tsx @@ -19,12 +19,12 @@ import { getSimplestDeckConfigForProtocol, } from '@opentrons/shared-data' -import { ChildNavigation } from '../ChildNavigation' -import { AddFixtureModal } from '../DeviceDetailsDeckConfiguration/AddFixtureModal' -import { DeckConfigurationDiscardChangesModal } from '../DeviceDetailsDeckConfiguration/DeckConfigurationDiscardChangesModal' -import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { getTopPortalEl } from '../../App/portal' -import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration' +import { ChildNavigation } from '../../../ChildNavigation' +import { AddFixtureModal } from '../../../DeviceDetailsDeckConfiguration/AddFixtureModal' +import { DeckConfigurationDiscardChangesModal } from '../../../DeviceDetailsDeckConfiguration/DeckConfigurationDiscardChangesModal' +import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' +import { getTopPortalEl } from '../../../../App/portal' +import { useNotifyDeckConfigurationQuery } from '../../../../resources/deck_configuration' import type { CutoutFixtureId, @@ -32,7 +32,7 @@ import type { ModuleModel, } from '@opentrons/shared-data' import type { ModuleOnDeck } from '@opentrons/components' -import type { SetupScreens } from '../../pages/ODD/ProtocolSetup' +import type { SetupScreens } from '../types' interface ProtocolSetupDeckConfigurationProps { cutoutId: CutoutId | null diff --git a/app/src/organisms/ProtocolSetupInstruments/index.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/ProtocolSetupInstruments.tsx similarity index 84% rename from app/src/organisms/ProtocolSetupInstruments/index.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/ProtocolSetupInstruments.tsx index fe6dfeb30b8..0a292625b30 100644 --- a/app/src/organisms/ProtocolSetupInstruments/index.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/ProtocolSetupInstruments.tsx @@ -11,16 +11,16 @@ import { TYPOGRAPHY, } from '@opentrons/components' import { useInstrumentsQuery } from '@opentrons/react-api-client' -import { ODDBackButton } from '../../molecules/ODDBackButton' -import { PipetteRecalibrationODDWarning } from '../../pages/ODD/InstrumentsDashboard/PipetteRecalibrationODDWarning' -import { getShowPipetteCalibrationWarning } from '../Devices/utils' -import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { ProtocolInstrumentMountItem } from '../InstrumentMountItem' +import { ODDBackButton } from '../../../../molecules/ODDBackButton' +import { PipetteRecalibrationODDWarning } from '../../../../pages/ODD/InstrumentsDashboard/PipetteRecalibrationODDWarning' +import { getShowPipetteCalibrationWarning } from '../../../Devices/utils' +import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' +import { ProtocolInstrumentMountItem } from '../../../InstrumentMountItem' import type { GripperData, PipetteData } from '@opentrons/api-client' import type { GripperModel } from '@opentrons/shared-data' -import type { SetupScreens } from '../../pages/ODD/ProtocolSetup' -import { isGripperInCommands } from '../../resources/protocols/utils' +import type { SetupScreens } from '../types' +import { isGripperInCommands } from '../../../../resources/protocols/utils' export interface ProtocolSetupInstrumentsProps { runId: string diff --git a/app/src/organisms/ProtocolSetupInstruments/__fixtures__/index.ts b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/__fixtures__/index.ts similarity index 100% rename from app/src/organisms/ProtocolSetupInstruments/__fixtures__/index.ts rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/__fixtures__/index.ts diff --git a/app/src/organisms/ProtocolSetupInstruments/__tests__/ProtocolSetupInstruments.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/__tests__/ProtocolSetupInstruments.test.tsx similarity index 83% rename from app/src/organisms/ProtocolSetupInstruments/__tests__/ProtocolSetupInstruments.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/__tests__/ProtocolSetupInstruments.test.tsx index bcfc0ecf1d6..14edc2b89f8 100644 --- a/app/src/organisms/ProtocolSetupInstruments/__tests__/ProtocolSetupInstruments.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/__tests__/ProtocolSetupInstruments.test.tsx @@ -9,18 +9,18 @@ import { useAllPipetteOffsetCalibrationsQuery, } from '@opentrons/react-api-client' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { useMostRecentCompletedAnalysis } from '../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { useIsOEMMode } from '../../../resources/robot-settings/hooks' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { i18n } from '../../../../../i18n' +import { useMostRecentCompletedAnalysis } from '../../../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' +import { useIsOEMMode } from '../../../../../resources/robot-settings/hooks' import { mockRecentAnalysis } from '../__fixtures__' import { ProtocolSetupInstruments } from '..' vi.mock('@opentrons/react-api-client') vi.mock( - '../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' + '../../../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' ) -vi.mock('../../../resources/robot-settings/hooks') +vi.mock('../../../../../resources/robot-settings/hooks') const mockGripperData = { instrumentModel: 'gripper_v1', diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/index.ts b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/index.ts new file mode 100644 index 00000000000..0d5d7d99a4d --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/index.ts @@ -0,0 +1,2 @@ +export * from './ProtocolSetupInstruments' +export * from './utils' diff --git a/app/src/organisms/ProtocolSetupInstruments/utils.ts b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/utils.ts similarity index 88% rename from app/src/organisms/ProtocolSetupInstruments/utils.ts rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/utils.ts index 1ce77275e74..ee19492827b 100644 --- a/app/src/organisms/ProtocolSetupInstruments/utils.ts +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/utils.ts @@ -1,7 +1,6 @@ import type { CompletedProtocolAnalysis, LoadedPipette, - ProtocolAnalysisOutput, } from '@opentrons/shared-data' import type { GripperData, @@ -9,16 +8,8 @@ import type { PipetteData, } from '@opentrons/api-client' -export function getProtocolUsesGripper( - analysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput -): boolean { - return ( - analysis?.commands.some( - c => - c.commandType === 'moveLabware' && c.params.strategy === 'usingGripper' - ) ?? false - ) -} +import { getProtocolUsesGripper } from '../../../../transformations/commands' + export function getAttachedGripper( attachedInstruments: Instruments ): GripperData | null { diff --git a/app/src/organisms/ProtocolSetupLabware/LabwareMapView.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/LabwareMapView.tsx similarity index 94% rename from app/src/organisms/ProtocolSetupLabware/LabwareMapView.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/LabwareMapView.tsx index 39be3dc0f05..d7003d8a4fa 100644 --- a/app/src/organisms/ProtocolSetupLabware/LabwareMapView.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/LabwareMapView.tsx @@ -7,8 +7,8 @@ import { THERMOCYCLER_MODULE_V1, } from '@opentrons/shared-data' -import { getStandardDeckViewLayerBlockList } from '../Devices/ProtocolRun/utils/getStandardDeckViewLayerBlockList' -import { getLabwareRenderInfo } from '../Devices/ProtocolRun/utils/getLabwareRenderInfo' +import { getStandardDeckViewLayerBlockList } from '../../../Devices/ProtocolRun/utils/getStandardDeckViewLayerBlockList' +import { getLabwareRenderInfo } from '../../../Devices/ProtocolRun/utils/getLabwareRenderInfo' import type { CompletedProtocolAnalysis, diff --git a/app/src/organisms/ProtocolSetupLabware/SingleLabwareModal.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/SingleLabwareModal.tsx similarity index 96% rename from app/src/organisms/ProtocolSetupLabware/SingleLabwareModal.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/SingleLabwareModal.tsx index e75ee87c6dd..c50becdfd95 100644 --- a/app/src/organisms/ProtocolSetupLabware/SingleLabwareModal.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/SingleLabwareModal.tsx @@ -17,8 +17,8 @@ import { } from '@opentrons/components' import { getLabwareDisplayName } from '@opentrons/shared-data' -import { getTopPortalEl } from '../../App/portal' -import { OddModal } from '../../molecules/OddModal' +import { getTopPortalEl } from '../../../../App/portal' +import { OddModal } from '../../../../molecules/OddModal' import type { CompletedProtocolAnalysis, diff --git a/app/src/organisms/ProtocolSetupLabware/__fixtures__/index.ts b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__fixtures__/index.ts similarity index 100% rename from app/src/organisms/ProtocolSetupLabware/__fixtures__/index.ts rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__fixtures__/index.ts diff --git a/app/src/organisms/ProtocolSetupLabware/__tests__/LabwareMapView.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__tests__/LabwareMapView.test.tsx similarity index 87% rename from app/src/organisms/ProtocolSetupLabware/__tests__/LabwareMapView.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__tests__/LabwareMapView.test.tsx index 532440223d2..ac20b68e8bd 100644 --- a/app/src/organisms/ProtocolSetupLabware/__tests__/LabwareMapView.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__tests__/LabwareMapView.test.tsx @@ -10,10 +10,10 @@ import { fixtureTiprack300ul, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { getLabwareRenderInfo } from '../../Devices/ProtocolRun/utils/getLabwareRenderInfo' -import { getStandardDeckViewLayerBlockList } from '../../Devices/ProtocolRun/utils/getStandardDeckViewLayerBlockList' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { i18n } from '../../../../../i18n' +import { getLabwareRenderInfo } from '../../../../Devices/ProtocolRun/utils/getLabwareRenderInfo' +import { getStandardDeckViewLayerBlockList } from '../../../../Devices/ProtocolRun/utils/getStandardDeckViewLayerBlockList' import { mockProtocolModuleInfo } from '../__fixtures__' import { LabwareMapView } from '../LabwareMapView' @@ -25,12 +25,12 @@ import type { ModuleModel, } from '@opentrons/shared-data' -vi.mock('../../Devices/ProtocolRun/utils/getLabwareRenderInfo') +vi.mock('../../../../Devices/ProtocolRun/utils/getLabwareRenderInfo') vi.mock('@opentrons/components/src/hardware-sim/Labware/LabwareRender') vi.mock('@opentrons/components/src/hardware-sim/BaseDeck') vi.mock('@opentrons/shared-data/js/helpers/getSimplestFlexDeckConfig') -vi.mock('../../../resources/deck_configuration/utils') -vi.mock('../../../redux/config') +vi.mock('../../../../../resources/deck_configuration/utils') +vi.mock('../../../../../redux/config') const MOCK_300_UL_TIPRACK_COORDS = [30, 40, 0] diff --git a/app/src/organisms/ProtocolSetupLabware/__tests__/ProtocolSetupLabware.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__tests__/ProtocolSetupLabware.test.tsx similarity index 88% rename from app/src/organisms/ProtocolSetupLabware/__tests__/ProtocolSetupLabware.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__tests__/ProtocolSetupLabware.test.tsx index 99b3c555dd5..174f0c7b72f 100644 --- a/app/src/organisms/ProtocolSetupLabware/__tests__/ProtocolSetupLabware.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__tests__/ProtocolSetupLabware.test.tsx @@ -13,10 +13,10 @@ import { ot3StandardDeckV5 as ot3StandardDeckDef, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { useMostRecentCompletedAnalysis } from '../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { getProtocolModulesInfo } from '../../Devices/ProtocolRun/utils/getProtocolModulesInfo' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { i18n } from '../../../../../i18n' +import { useMostRecentCompletedAnalysis } from '../../../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' +import { getProtocolModulesInfo } from '../../../../Devices/ProtocolRun/utils/getProtocolModulesInfo' import { ProtocolSetupLabware } from '..' import { mockProtocolModuleInfo, @@ -27,7 +27,7 @@ import { mockUseModulesQueryOpening, mockUseModulesQueryUnknown, } from '../__fixtures__' -import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration' +import { useNotifyDeckConfigurationQuery } from '../../../../../resources/deck_configuration' import type * as ReactApiClient from '@opentrons/react-api-client' @@ -41,10 +41,10 @@ vi.mock('@opentrons/react-api-client', async importOriginal => { }) vi.mock( - '../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' + '../../../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' ) -vi.mock('../../Devices/ProtocolRun/utils/getProtocolModulesInfo') -vi.mock('../../../resources/deck_configuration') +vi.mock('../../../../Devices/ProtocolRun/utils/getProtocolModulesInfo') +vi.mock('../../../../../resources/deck_configuration') const RUN_ID = "otie's run" const mockSetSetupScreen = vi.fn() diff --git a/app/src/organisms/ProtocolSetupLabware/index.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/index.tsx similarity index 95% rename from app/src/organisms/ProtocolSetupLabware/index.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/index.tsx index 52c0f8d115d..092138b8b2f 100644 --- a/app/src/organisms/ProtocolSetupLabware/index.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/index.tsx @@ -35,14 +35,14 @@ import { useModulesQuery, } from '@opentrons/react-api-client' -import { FloatingActionButton, SmallButton } from '../../atoms/buttons' -import { ODDBackButton } from '../../molecules/ODDBackButton' -import { getLabwareSetupItemGroups } from '../../pages/Desktop/Protocols/utils' -import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration' -import { getProtocolModulesInfo } from '../Devices/ProtocolRun/utils/getProtocolModulesInfo' -import { getNestedLabwareInfo } from '../Devices/ProtocolRun/SetupLabware/getNestedLabwareInfo' -import { LabwareStackModal } from '../Devices/ProtocolRun/SetupLabware/LabwareStackModal' -import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis' +import { FloatingActionButton, SmallButton } from '../../../../atoms/buttons' +import { ODDBackButton } from '../../../../molecules/ODDBackButton' +import { getLabwareSetupItemGroups } from '../../../../transformations/commands' +import { useNotifyDeckConfigurationQuery } from '../../../../resources/deck_configuration' +import { getProtocolModulesInfo } from '../../../Devices/ProtocolRun/utils/getProtocolModulesInfo' +import { getNestedLabwareInfo } from '../../../Devices/ProtocolRun/SetupLabware/getNestedLabwareInfo' +import { LabwareStackModal } from '../../../Devices/ProtocolRun/SetupLabware/LabwareStackModal' +import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' import { getAttachedProtocolModuleMatches } from '../ProtocolSetupModulesAndDeck/utils' import { LabwareMapView } from './LabwareMapView' import { SingleLabwareModal } from './SingleLabwareModal' @@ -57,9 +57,9 @@ import type { RunTimeCommand, } from '@opentrons/shared-data' import type { HeaterShakerModule, Modules } from '@opentrons/api-client' -import type { LabwareSetupItem } from '../../pages/Desktop/Protocols/utils' -import type { SetupScreens } from '../../pages/ODD/ProtocolSetup' -import type { NestedLabwareInfo } from '../Devices/ProtocolRun/SetupLabware/getNestedLabwareInfo' +import type { LabwareSetupItem } from '../../../../transformations/commands' +import type { SetupScreens } from '../types' +import type { NestedLabwareInfo } from '../../../Devices/ProtocolRun/SetupLabware/getNestedLabwareInfo' import type { AttachedProtocolModuleMatch } from '../ProtocolSetupModulesAndDeck/utils' const MODULE_REFETCH_INTERVAL_MS = 5000 diff --git a/app/src/organisms/ProtocolSetupLiquids/LiquidDetails.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/LiquidDetails.tsx similarity index 94% rename from app/src/organisms/ProtocolSetupLiquids/LiquidDetails.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/LiquidDetails.tsx index e117134ba77..0b1651ad0ae 100644 --- a/app/src/organisms/ProtocolSetupLiquids/LiquidDetails.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/LiquidDetails.tsx @@ -14,9 +14,9 @@ import { WRAP, } from '@opentrons/components' import { MICRO_LITERS } from '@opentrons/shared-data' -import { LiquidsLabwareDetailsModal } from '../Devices/ProtocolRun/SetupLiquids/LiquidsLabwareDetailsModal' -import { getLocationInfoNames } from '../Devices/ProtocolRun/utils/getLocationInfoNames' -import { getVolumePerWell } from '../Devices/ProtocolRun/SetupLiquids/utils' +import { LiquidsLabwareDetailsModal } from '../../../Devices/ProtocolRun/SetupLiquids/LiquidsLabwareDetailsModal' +import { getLocationInfoNames } from '../../../Devices/ProtocolRun/utils/getLocationInfoNames' +import { getVolumePerWell } from '../../../Devices/ProtocolRun/SetupLiquids/utils' import type { LabwareByLiquidId, diff --git a/app/src/organisms/ProtocolSetupLiquids/__tests__/LiquidDetails.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/__tests__/LiquidDetails.test.tsx similarity index 72% rename from app/src/organisms/ProtocolSetupLiquids/__tests__/LiquidDetails.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/__tests__/LiquidDetails.test.tsx index e7a7fafc33d..b1e4103a6c0 100644 --- a/app/src/organisms/ProtocolSetupLiquids/__tests__/LiquidDetails.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/__tests__/LiquidDetails.test.tsx @@ -2,22 +2,24 @@ import * as React from 'react' import { screen, fireEvent } from '@testing-library/react' import { describe, it, beforeEach, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { RUN_ID_1 } from '../../RunTimeControl/__fixtures__' -import { getLocationInfoNames } from '../../Devices/ProtocolRun/utils/getLocationInfoNames' -import { getVolumePerWell } from '../../Devices/ProtocolRun/SetupLiquids/utils' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { i18n } from '../../../../../i18n' +import { RUN_ID_1 } from '../../../../RunTimeControl/__fixtures__' +import { getLocationInfoNames } from '../../../../Devices/ProtocolRun/utils/getLocationInfoNames' +import { getVolumePerWell } from '../../../../Devices/ProtocolRun/SetupLiquids/utils' import { LiquidDetails } from '../LiquidDetails' -import { LiquidsLabwareDetailsModal } from '../../Devices/ProtocolRun/SetupLiquids/LiquidsLabwareDetailsModal' +import { LiquidsLabwareDetailsModal } from '../../../../Devices/ProtocolRun/SetupLiquids/LiquidsLabwareDetailsModal' import { MOCK_LABWARE_INFO_BY_LIQUID_ID, MOCK_PROTOCOL_ANALYSIS, } from '../fixtures' import type { CompletedProtocolAnalysis } from '@opentrons/shared-data' -vi.mock('../../Devices/ProtocolRun/SetupLiquids/utils') -vi.mock('../../Devices/ProtocolRun/utils/getLocationInfoNames') -vi.mock('../../Devices/ProtocolRun/SetupLiquids/LiquidsLabwareDetailsModal') +vi.mock('../../../../Devices/ProtocolRun/SetupLiquids/utils') +vi.mock('../../../../Devices/ProtocolRun/utils/getLocationInfoNames') +vi.mock( + '../../../../Devices/ProtocolRun/SetupLiquids/LiquidsLabwareDetailsModal' +) const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/ProtocolSetupLiquids/__tests__/ProtocolSetupLiquids.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/__tests__/ProtocolSetupLiquids.test.tsx similarity index 79% rename from app/src/organisms/ProtocolSetupLiquids/__tests__/ProtocolSetupLiquids.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/__tests__/ProtocolSetupLiquids.test.tsx index b4dd41061e6..e3e77ab3681 100644 --- a/app/src/organisms/ProtocolSetupLiquids/__tests__/ProtocolSetupLiquids.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/__tests__/ProtocolSetupLiquids.test.tsx @@ -7,11 +7,11 @@ import { parseLiquidsInLoadOrder, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { RUN_ID_1 } from '../../RunTimeControl/__fixtures__' -import { getTotalVolumePerLiquidId } from '../../Devices/ProtocolRun/SetupLiquids/utils' -import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { i18n } from '../../../../../i18n' +import { RUN_ID_1 } from '../../../../RunTimeControl/__fixtures__' +import { getTotalVolumePerLiquidId } from '../../../../Devices/ProtocolRun/SetupLiquids/utils' +import { useMostRecentCompletedAnalysis } from '../../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' import { LiquidDetails } from '../LiquidDetails' import { MOCK_LABWARE_INFO_BY_LIQUID_ID, @@ -22,10 +22,10 @@ import { ProtocolSetupLiquids } from '..' import type * as SharedData from '@opentrons/shared-data' -vi.mock('../../Devices/ProtocolRun/SetupLiquids/utils') -vi.mock('../../../atoms/buttons') +vi.mock('../../../../Devices/ProtocolRun/SetupLiquids/utils') +vi.mock('../../../../../atoms/buttons') vi.mock('../LiquidDetails') -vi.mock('../../LabwarePositionCheck/useMostRecentCompletedAnalysis') +vi.mock('../../../../LabwarePositionCheck/useMostRecentCompletedAnalysis') vi.mock('@opentrons/shared-data', async importOriginal => { const actualSharedData = await importOriginal() return { diff --git a/app/src/organisms/ProtocolSetupLiquids/fixtures.ts b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/fixtures.ts similarity index 100% rename from app/src/organisms/ProtocolSetupLiquids/fixtures.ts rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/fixtures.ts diff --git a/app/src/organisms/ProtocolSetupLiquids/index.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/index.tsx similarity index 93% rename from app/src/organisms/ProtocolSetupLiquids/index.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/index.tsx index a39d547d2ed..14290caf69b 100644 --- a/app/src/organisms/ProtocolSetupLiquids/index.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/index.tsx @@ -19,14 +19,14 @@ import { parseLabwareInfoByLiquidId, parseLiquidsInLoadOrder, } from '@opentrons/shared-data' -import { ODDBackButton } from '../../molecules/ODDBackButton' -import { SmallButton } from '../../atoms/buttons' +import { ODDBackButton } from '../../../../molecules/ODDBackButton' +import { SmallButton } from '../../../../atoms/buttons' -import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { getTotalVolumePerLiquidId } from '../Devices/ProtocolRun/SetupLiquids/utils' +import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' +import { getTotalVolumePerLiquidId } from '../../../Devices/ProtocolRun/SetupLiquids/utils' import { LiquidDetails } from './LiquidDetails' import type { ParsedLiquid, RunTimeCommand } from '@opentrons/shared-data' -import type { SetupScreens } from '../../pages/ODD/ProtocolSetup' +import type { SetupScreens } from '../types' export interface ProtocolSetupLiquidsProps { runId: string diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/FixtureTable.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/FixtureTable.tsx similarity index 92% rename from app/src/organisms/ProtocolSetupModulesAndDeck/FixtureTable.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/FixtureTable.tsx index 636d039fc5c..904ce8513ce 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/FixtureTable.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/FixtureTable.tsx @@ -22,10 +22,10 @@ import { SINGLE_SLOT_FIXTURES, } from '@opentrons/shared-data' -import { SmallButton } from '../../atoms/buttons' -import { useDeckConfigurationCompatibility } from '../../resources/deck_configuration/hooks' -import { getRequiredDeckConfig } from '../../resources/deck_configuration/utils' -import { LocationConflictModal } from '../Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal' +import { SmallButton } from '../../../../atoms/buttons' +import { useDeckConfigurationCompatibility } from '../../../../resources/deck_configuration/hooks' +import { getRequiredDeckConfig } from '../../../../resources/deck_configuration/utils' +import { LocationConflictModal } from '../../../Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal' import type { CompletedProtocolAnalysis, @@ -34,10 +34,10 @@ import type { DeckDefinition, RobotType, } from '@opentrons/shared-data' -import type { SetupScreens } from '../../pages/ODD/ProtocolSetup' -import type { CutoutConfigAndCompatibility } from '../../resources/deck_configuration/hooks' +import type { SetupScreens } from '../types' +import type { CutoutConfigAndCompatibility } from '../../../../resources/deck_configuration/hooks' import { useSelector } from 'react-redux' -import { getLocalRobot } from '../../redux/discovery' +import { getLocalRobot } from '../../../../redux/discovery' interface FixtureTableProps { robotType: RobotType diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/ModuleTable.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/ModuleTable.tsx similarity index 90% rename from app/src/organisms/ProtocolSetupModulesAndDeck/ModuleTable.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/ModuleTable.tsx index bc6313d0626..664369120b4 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/ModuleTable.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/ModuleTable.tsx @@ -25,21 +25,21 @@ import { THERMOCYCLER_MODULE_TYPE, } from '@opentrons/shared-data' -import { SmallButton } from '../../atoms/buttons' -import { getModulePrepCommands } from '../../organisms/Devices/getModulePrepCommands' -import { getModuleTooHot } from '../../organisms/Devices/getModuleTooHot' -import { useRunCalibrationStatus } from '../../organisms/Devices/hooks' -import { LocationConflictModal } from '../../organisms/Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal' -import { ModuleWizardFlows } from '../../organisms/ModuleWizardFlows' -import { useToaster } from '../../organisms/ToasterOven' -import { getLocalRobot } from '../../redux/discovery' -import { useChainLiveCommands } from '../../resources/runs' -import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration' +import { SmallButton } from '../../../../atoms/buttons' +import { getModulePrepCommands } from '../../../Devices/getModulePrepCommands' +import { getModuleTooHot } from '../../../Devices/getModuleTooHot' +import { useRunCalibrationStatus } from '../../../Devices/hooks' +import { LocationConflictModal } from '../../../Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal' +import { ModuleWizardFlows } from '../../../ModuleWizardFlows' +import { useToaster } from '../../../ToasterOven' +import { getLocalRobot } from '../../../../redux/discovery' +import { useChainLiveCommands } from '../../../../resources/runs' +import { useNotifyDeckConfigurationQuery } from '../../../../resources/deck_configuration' import type { CommandData } from '@opentrons/api-client' import type { CutoutConfig, DeckDefinition } from '@opentrons/shared-data' -import type { ModulePrepCommandsType } from '../../organisms/Devices/getModulePrepCommands' -import type { ProtocolCalibrationStatus } from '../../organisms/Devices/hooks' +import type { ModulePrepCommandsType } from '../../../Devices/getModulePrepCommands' +import type { ProtocolCalibrationStatus } from '../../../Devices/hooks' import type { AttachedProtocolModuleMatch } from './utils' const DECK_CONFIG_REFETCH_INTERVAL = 5000 diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/ModulesAndDeckMapView.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/ModulesAndDeckMapView.tsx similarity index 89% rename from app/src/organisms/ProtocolSetupModulesAndDeck/ModulesAndDeckMapView.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/ModulesAndDeckMapView.tsx index a6c3c6dddaa..355da533ba1 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/ModulesAndDeckMapView.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/ModulesAndDeckMapView.tsx @@ -6,8 +6,8 @@ import { getSimplestDeckConfigForProtocol, } from '@opentrons/shared-data' -import { ModuleInfo } from '../Devices/ModuleInfo' -import { getStandardDeckViewLayerBlockList } from '../Devices/ProtocolRun/utils/getStandardDeckViewLayerBlockList' +import { ModuleInfo } from '../../../Devices/ModuleInfo' +import { getStandardDeckViewLayerBlockList } from '../../../Devices/ProtocolRun/utils/getStandardDeckViewLayerBlockList' import type { CompletedProtocolAnalysis } from '@opentrons/shared-data' import type { AttachedProtocolModuleMatch } from './utils' diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/index.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/ProtocolSetupModulesAndDeck.tsx similarity index 88% rename from app/src/organisms/ProtocolSetupModulesAndDeck/index.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/ProtocolSetupModulesAndDeck.tsx index be31a854667..c32330b27e3 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/index.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/ProtocolSetupModulesAndDeck.tsx @@ -16,14 +16,14 @@ import { } from '@opentrons/shared-data' import { RUN_STATUS_STOPPED } from '@opentrons/api-client' -import { getTopPortalEl } from '../../App/portal' -import { FloatingActionButton } from '../../atoms/buttons' -import { InlineNotification } from '../../atoms/InlineNotification' -import { ChildNavigation } from '../../organisms/ChildNavigation' -import { useAttachedModules } from '../../organisms/Devices/hooks' -import { getProtocolModulesInfo } from '../../organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo' -import { useMostRecentCompletedAnalysis } from '../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { useRunStatus } from '../RunTimeControl/hooks' +import { getTopPortalEl } from '../../../../App/portal' +import { FloatingActionButton } from '../../../../atoms/buttons' +import { InlineNotification } from '../../../../atoms/InlineNotification' +import { ChildNavigation } from '../../../../organisms/ChildNavigation' +import { useAttachedModules } from '../../../../organisms/Devices/hooks' +import { getProtocolModulesInfo } from '../../../../organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo' +import { useMostRecentCompletedAnalysis } from '../../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' +import { useRunStatus } from '../../../RunTimeControl/hooks' import { getAttachedProtocolModuleMatches, getUnmatchedModulesForProtocol, @@ -32,10 +32,10 @@ import { SetupInstructionsModal } from './SetupInstructionsModal' import { FixtureTable } from './FixtureTable' import { ModuleTable } from './ModuleTable' import { ModulesAndDeckMapView } from './ModulesAndDeckMapView' -import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration' +import { useNotifyDeckConfigurationQuery } from '../../../../resources/deck_configuration' import type { CutoutId, CutoutFixtureId } from '@opentrons/shared-data' -import type { SetupScreens } from '../../pages/ODD/ProtocolSetup' +import type { SetupScreens } from '../types' const ATTACHED_MODULE_POLL_MS = 5000 const DECK_CONFIG_POLL_MS = 5000 diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/SetupInstructionsModal.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/SetupInstructionsModal.tsx similarity index 88% rename from app/src/organisms/ProtocolSetupModulesAndDeck/SetupInstructionsModal.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/SetupInstructionsModal.tsx index 569c59f6c72..2c4e141a1db 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/SetupInstructionsModal.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/SetupInstructionsModal.tsx @@ -12,11 +12,11 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { OddModal } from '../../molecules/OddModal' +import { OddModal } from '../../../../molecules/OddModal' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '../../../../molecules/OddModal/types' -import imgSrc from '../../assets/images/on-device-display/setup_instructions_qr_code.png' +import imgSrc from '../../../../assets/images/on-device-display/setup_instructions_qr_code.png' const INSTRUCTIONS_URL = 'support.opentrons.com/s/modules' diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/FixtureTable.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/FixtureTable.test.tsx similarity index 82% rename from app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/FixtureTable.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/FixtureTable.test.tsx index c73a5aacd79..6e7769440ea 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/FixtureTable.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/FixtureTable.test.tsx @@ -10,18 +10,18 @@ import { TRASH_BIN_ADAPTER_FIXTURE, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { LocationConflictModal } from '../../../organisms/Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal' -import { useDeckConfigurationCompatibility } from '../../../resources/deck_configuration/hooks' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { i18n } from '../../../../../i18n' +import { LocationConflictModal } from '../../../../../organisms/Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal' +import { useDeckConfigurationCompatibility } from '../../../../../resources/deck_configuration/hooks' import { FixtureTable } from '../FixtureTable' -import { getLocalRobot } from '../../../redux/discovery' -import { mockConnectedRobot } from '../../../redux/discovery/__fixtures__' +import { getLocalRobot } from '../../../../../redux/discovery' +import { mockConnectedRobot } from '../../../../../redux/discovery/__fixtures__' -vi.mock('../../../redux/discovery') -vi.mock('../../../resources/deck_configuration/hooks') +vi.mock('../../../../../redux/discovery') +vi.mock('../../../../../resources/deck_configuration/hooks') vi.mock( - '../../../organisms/Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal' + '../../../../../organisms/Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal' ) const mockSetSetupScreen = vi.fn() diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ModulesAndDeckMapView.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/ModulesAndDeckMapView.test.tsx similarity index 89% rename from app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ModulesAndDeckMapView.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/ModulesAndDeckMapView.test.tsx index e0551b3f4f3..14ae921ee15 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ModulesAndDeckMapView.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/ModulesAndDeckMapView.test.tsx @@ -8,18 +8,18 @@ import { getSimplestDeckConfigForProtocol, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { i18n } from '../../../../../i18n' import { ModulesAndDeckMapView } from '../ModulesAndDeckMapView' vi.mock('@opentrons/components/src/hardware-sim/BaseDeck') vi.mock('@opentrons/api-client') vi.mock('@opentrons/shared-data/js/helpers/getSimplestFlexDeckConfig') -vi.mock('../../../redux/config') -vi.mock('../../Devices/hooks') -vi.mock('../../../resources/deck_configuration/utils') -vi.mock('../../Devices/ModuleInfo') -vi.mock('../../Devices/ProtocolRun/utils/getLabwareRenderInfo') +vi.mock('../../../../../redux/config') +vi.mock('../../../../Devices/hooks') +vi.mock('../../../../../resources/deck_configuration/utils') +vi.mock('../../../../Devices/ModuleInfo') +vi.mock('../../../../Devices/ProtocolRun/utils/getLabwareRenderInfo') const mockRunId = 'mockRunId' const PROTOCOL_ANALYSIS = { diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx similarity index 86% rename from app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx index 9f9cec5524d..cb268c18906 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx @@ -11,51 +11,55 @@ import { getDeckDefFromRobotType, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { useChainLiveCommands } from '../../../resources/runs' -import { mockRobotSideAnalysis } from '../../../molecules/Command/__fixtures__' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { i18n } from '../../../../../i18n' +import { useChainLiveCommands } from '../../../../../resources/runs' +import { mockRobotSideAnalysis } from '../../../../../molecules/Command/__fixtures__' import { useAttachedModules, useRunCalibrationStatus, -} from '../../Devices/hooks' -import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { getProtocolModulesInfo } from '../../Devices/ProtocolRun/utils/getProtocolModulesInfo' -import { mockApiHeaterShaker } from '../../../redux/modules/__fixtures__' +} from '../../../../Devices/hooks' +import { useMostRecentCompletedAnalysis } from '../../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' +import { getProtocolModulesInfo } from '../../../../Devices/ProtocolRun/utils/getProtocolModulesInfo' +import { mockApiHeaterShaker } from '../../../../../redux/modules/__fixtures__' import { mockProtocolModuleInfo } from '../../ProtocolSetupInstruments/__fixtures__' -import { getLocalRobot } from '../../../redux/discovery' -import { mockConnectedRobot } from '../../../redux/discovery/__fixtures__' +import { getLocalRobot } from '../../../../../redux/discovery' +import { mockConnectedRobot } from '../../../../../redux/discovery/__fixtures__' import { getAttachedProtocolModuleMatches, getUnmatchedModulesForProtocol, } from '../utils' -import { LocationConflictModal } from '../../Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal' -import { ModuleWizardFlows } from '../../ModuleWizardFlows' +import { LocationConflictModal } from '../../../../Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal' +import { ModuleWizardFlows } from '../../../../ModuleWizardFlows' import { SetupInstructionsModal } from '../SetupInstructionsModal' import { FixtureTable } from '../FixtureTable' import { ModulesAndDeckMapView } from '../ModulesAndDeckMapView' import { ProtocolSetupModulesAndDeck } from '..' -import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration' -import { useRunStatus } from '../../RunTimeControl/hooks' +import { useNotifyDeckConfigurationQuery } from '../../../../../resources/deck_configuration' +import { useRunStatus } from '../../../../RunTimeControl/hooks' import type { CutoutConfig, DeckConfiguration } from '@opentrons/shared-data' import type { UseQueryResult } from 'react-query' -vi.mock('../../../resources/runs') -vi.mock('../../../redux/discovery') -vi.mock('../../../organisms/Devices/hooks') -vi.mock('../../../resources/deck_configuration') +vi.mock('../../../../../resources/runs') +vi.mock('../../../../../redux/discovery') +vi.mock('../../../../../organisms/Devices/hooks') +vi.mock('../../../../../resources/deck_configuration') vi.mock( - '../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' + '../../../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' +) +vi.mock( + '../../../../../organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo' ) -vi.mock('../../../organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo') vi.mock('../utils') vi.mock('../SetupInstructionsModal') -vi.mock('../../ModuleWizardFlows') +vi.mock('../../../../ModuleWizardFlows') vi.mock('../FixtureTable') -vi.mock('../../Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal') +vi.mock( + '../../../../Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal' +) vi.mock('../ModulesAndDeckMapView') -vi.mock('../../RunTimeControl/hooks') +vi.mock('../../../../RunTimeControl/hooks') const ROBOT_NAME = 'otie' const RUN_ID = '1' diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/SetupInstructionsModal.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/SetupInstructionsModal.test.tsx similarity index 92% rename from app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/SetupInstructionsModal.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/SetupInstructionsModal.test.tsx index 06db135f3f6..65b1949cef2 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/SetupInstructionsModal.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/SetupInstructionsModal.test.tsx @@ -2,8 +2,8 @@ import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, beforeEach, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { i18n } from '../../../../../i18n' import { SetupInstructionsModal } from '../SetupInstructionsModal' diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/utils.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/utils.test.tsx similarity index 97% rename from app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/utils.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/utils.test.tsx index b96d972ca36..852c5f04e11 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/utils.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/utils.test.tsx @@ -4,7 +4,7 @@ import { getModuleDef2, } from '@opentrons/shared-data' -import { mockTemperatureModuleGen2 } from '../../../redux/modules/__fixtures__' +import { mockTemperatureModuleGen2 } from '../../../../../redux/modules/__fixtures__' import { getAttachedProtocolModuleMatches, getUnmatchedModulesForProtocol, diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/index.ts b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/index.ts new file mode 100644 index 00000000000..7265f1a9b8c --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/index.ts @@ -0,0 +1,2 @@ +export * from './ProtocolSetupModulesAndDeck' +export { getUnmatchedModulesForProtocol } from './utils' diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/utils.ts b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/utils.ts similarity index 96% rename from app/src/organisms/ProtocolSetupModulesAndDeck/utils.ts rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/utils.ts index f1b8601dca1..fb4ecb5ad7f 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/utils.ts +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/utils.ts @@ -10,8 +10,8 @@ import { } from '@opentrons/shared-data' import type { DeckConfiguration, RobotType } from '@opentrons/shared-data' -import type { ProtocolModuleInfo } from '../../organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo' -import type { AttachedModule } from '../../redux/modules/types' +import type { ProtocolModuleInfo } from '../../../Devices/ProtocolRun/utils/getProtocolModulesInfo' +import type { AttachedModule } from '../../../../redux/modules/types' export type AttachedProtocolModuleMatch = ProtocolModuleInfo & { attachedModuleMatch: AttachedModule | null diff --git a/app/src/organisms/ProtocolSetupOffsets/index.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupOffsets/index.tsx similarity index 83% rename from app/src/organisms/ProtocolSetupOffsets/index.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupOffsets/index.tsx index bc4ad45ae52..85395e5a085 100644 --- a/app/src/organisms/ProtocolSetupOffsets/index.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupOffsets/index.tsx @@ -12,15 +12,15 @@ import { } from '@opentrons/components' import type { LabwareOffset } from '@opentrons/api-client' -import { useToaster } from '../../organisms/ToasterOven' -import { ODDBackButton } from '../../molecules/ODDBackButton' -import { FloatingActionButton, SmallButton } from '../../atoms/buttons' -import type { SetupScreens } from '../../pages/ODD/ProtocolSetup' -import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { TerseOffsetTable } from '../../organisms/LabwarePositionCheck/ResultsSummary' -import { getLabwareDefinitionsFromCommands } from '../../molecules/Command/utils/getLabwareDefinitionsFromCommands' -import { useNotifyRunQuery } from '../../resources/runs' -import { getLatestCurrentOffsets } from '../../organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/utils' +import { useToaster } from '../../../../organisms/ToasterOven' +import { ODDBackButton } from '../../../../molecules/ODDBackButton' +import { FloatingActionButton, SmallButton } from '../../../../atoms/buttons' +import type { SetupScreens } from '../types' +import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' +import { TerseOffsetTable } from '../../../../organisms/LabwarePositionCheck/ResultsSummary' +import { getLabwareDefinitionsFromCommands } from '../../../../molecules/Command/utils/getLabwareDefinitionsFromCommands' +import { useNotifyRunQuery } from '../../../../resources/runs' +import { getLatestCurrentOffsets } from '../../../../organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/utils' export interface ProtocolSetupOffsetsProps { runId: string diff --git a/app/src/organisms/ProtocolSetupParameters/AnalysisFailed.stories.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/AnalysisFailed.stories.tsx similarity index 89% rename from app/src/organisms/ProtocolSetupParameters/AnalysisFailed.stories.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/AnalysisFailed.stories.tsx index 2b865e5fb9c..92717171bb6 100644 --- a/app/src/organisms/ProtocolSetupParameters/AnalysisFailed.stories.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/AnalysisFailed.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react' -import { VIEWPORT } from '../../../../components/src/ui-style-constants' +import { VIEWPORT } from '../../../../../../components/src/ui-style-constants' import { AnalysisFailedModal } from './AnalysisFailedModal' import type { Story, Meta } from '@storybook/react' diff --git a/app/src/organisms/ProtocolSetupParameters/AnalysisFailedModal.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/AnalysisFailedModal.tsx similarity index 92% rename from app/src/organisms/ProtocolSetupParameters/AnalysisFailedModal.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/AnalysisFailedModal.tsx index 059a59d5532..a993a0a8b43 100644 --- a/app/src/organisms/ProtocolSetupParameters/AnalysisFailedModal.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/AnalysisFailedModal.tsx @@ -11,10 +11,10 @@ import { } from '@opentrons/components' import { useDismissCurrentRunMutation } from '@opentrons/react-api-client' -import { SmallButton } from '../../atoms/buttons' -import { OddModal } from '../../molecules/OddModal' +import { SmallButton } from '../../../../atoms/buttons' +import { OddModal } from '../../../../molecules/OddModal' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '../../../../molecules/OddModal/types' interface AnalysisFailedModalProps { errors: string[] diff --git a/app/src/organisms/ProtocolSetupParameters/ChooseCsvFile.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseCsvFile.tsx similarity index 97% rename from app/src/organisms/ProtocolSetupParameters/ChooseCsvFile.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseCsvFile.tsx index 9604eeec153..309d3350724 100644 --- a/app/src/organisms/ProtocolSetupParameters/ChooseCsvFile.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseCsvFile.tsx @@ -18,8 +18,8 @@ import { } from '@opentrons/components' import { useAllCsvFilesQuery } from '@opentrons/react-api-client' -import { getShellUpdateDataFiles } from '../../redux/shell' -import { ChildNavigation } from '../ChildNavigation' +import { getShellUpdateDataFiles } from '../../../../redux/shell' +import { ChildNavigation } from '../../../ChildNavigation' import { EmptyFile } from './EmptyFile' import type { diff --git a/app/src/organisms/ProtocolSetupParameters/ChooseEnum.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseEnum.tsx similarity index 95% rename from app/src/organisms/ProtocolSetupParameters/ChooseEnum.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseEnum.tsx index 0ad856f5981..a4723edf734 100644 --- a/app/src/organisms/ProtocolSetupParameters/ChooseEnum.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseEnum.tsx @@ -9,8 +9,8 @@ import { RadioButton, TYPOGRAPHY, } from '@opentrons/components' -import { useToaster } from '../ToasterOven' -import { ChildNavigation } from '../ChildNavigation' +import { useToaster } from '../../../ToasterOven' +import { ChildNavigation } from '../../../ChildNavigation' import type { ChoiceParameter } from '@opentrons/shared-data' interface ChooseEnumProps { diff --git a/app/src/organisms/ProtocolSetupParameters/ChooseNumber.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseNumber.tsx similarity index 96% rename from app/src/organisms/ProtocolSetupParameters/ChooseNumber.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseNumber.tsx index ed6918f9aa8..2a2fe6a4dc6 100644 --- a/app/src/organisms/ProtocolSetupParameters/ChooseNumber.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseNumber.tsx @@ -11,9 +11,9 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { useToaster } from '../ToasterOven' -import { ChildNavigation } from '../ChildNavigation' -import { NumericalKeyboard } from '../../atoms/SoftwareKeyboard' +import { useToaster } from '../../../ToasterOven' +import { ChildNavigation } from '../../../ChildNavigation' +import { NumericalKeyboard } from '../../../../atoms/SoftwareKeyboard' import type { NumberParameter } from '@opentrons/shared-data' interface ChooseNumberProps { diff --git a/app/src/organisms/ProtocolSetupParameters/EmptyFile.stories.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/EmptyFile.stories.tsx similarity index 100% rename from app/src/organisms/ProtocolSetupParameters/EmptyFile.stories.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/EmptyFile.stories.tsx diff --git a/app/src/organisms/ProtocolSetupParameters/EmptyFile.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/EmptyFile.tsx similarity index 100% rename from app/src/organisms/ProtocolSetupParameters/EmptyFile.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/EmptyFile.tsx diff --git a/app/src/organisms/ProtocolSetupParameters/index.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ProtocolSetupParameters.tsx similarity index 97% rename from app/src/organisms/ProtocolSetupParameters/index.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ProtocolSetupParameters.tsx index 38c5ca43d84..56a76db6dcb 100644 --- a/app/src/organisms/ProtocolSetupParameters/index.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ProtocolSetupParameters.tsx @@ -22,14 +22,14 @@ import { import { getRunTimeParameterFilesForRun, getRunTimeParameterValuesForRun, -} from '../Devices/utils' -import { ChildNavigation } from '../ChildNavigation' +} from '../../../Devices/utils' +import { ChildNavigation } from '../../../ChildNavigation' import { ResetValuesModal } from './ResetValuesModal' import { ChooseEnum } from './ChooseEnum' import { ChooseNumber } from './ChooseNumber' import { ChooseCsvFile } from './ChooseCsvFile' -import { useToaster } from '../ToasterOven' -import { ProtocolSetupStep } from '../../pages/ODD/ProtocolSetup' +import { useToaster } from '../../../ToasterOven' +import { ProtocolSetupStep } from '../ProtocolSetupStep' import type { CompletedProtocolAnalysis, ChoiceParameter, @@ -39,7 +39,7 @@ import type { ValueRunTimeParameter, CsvFileParameterFileData, } from '@opentrons/shared-data' -import type { ProtocolSetupStepStatus } from '../../pages/ODD/ProtocolSetup' +import type { ProtocolSetupStepStatus } from '../ProtocolSetupStep' import type { FileData, LabwareOffsetCreateData } from '@opentrons/api-client' interface ProtocolSetupParametersProps { diff --git a/app/src/organisms/ProtocolSetupParameters/ResetValuesModal.stories.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ResetValuesModal.stories.tsx similarity index 100% rename from app/src/organisms/ProtocolSetupParameters/ResetValuesModal.stories.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ResetValuesModal.stories.tsx diff --git a/app/src/organisms/ProtocolSetupParameters/ResetValuesModal.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ResetValuesModal.tsx similarity index 91% rename from app/src/organisms/ProtocolSetupParameters/ResetValuesModal.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ResetValuesModal.tsx index 2ecc6021618..64468e9176e 100644 --- a/app/src/organisms/ProtocolSetupParameters/ResetValuesModal.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ResetValuesModal.tsx @@ -11,11 +11,11 @@ import { LegacyStyledText, } from '@opentrons/components' -import { SmallButton } from '../../atoms/buttons' -import { OddModal } from '../../molecules/OddModal' +import { SmallButton } from '../../../../atoms/buttons' +import { OddModal } from '../../../../molecules/OddModal' import type { RunTimeParameter } from '@opentrons/shared-data' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '../../../../molecules/OddModal/types' interface ResetValuesModalProps { runTimeParametersOverrides: RunTimeParameter[] diff --git a/app/src/organisms/ProtocolSetupParameters/ViewOnlyParameters.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ViewOnlyParameters.tsx similarity index 93% rename from app/src/organisms/ProtocolSetupParameters/ViewOnlyParameters.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ViewOnlyParameters.tsx index b81058490ba..2339ae7cc88 100644 --- a/app/src/organisms/ProtocolSetupParameters/ViewOnlyParameters.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ViewOnlyParameters.tsx @@ -18,11 +18,11 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { ChildNavigation } from '../ChildNavigation' -import { useToaster } from '../ToasterOven' +import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' +import { ChildNavigation } from '../../../ChildNavigation' +import { useToaster } from '../../../ToasterOven' -import type { SetupScreens } from '../../pages/ODD/ProtocolSetup' +import type { SetupScreens } from '../types' export interface ViewOnlyParametersProps { runId: string diff --git a/app/src/organisms/ProtocolSetupParameters/__tests__/AnalysisFailedModal.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/AnalysisFailedModal.test.tsx similarity index 95% rename from app/src/organisms/ProtocolSetupParameters/__tests__/AnalysisFailedModal.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/AnalysisFailedModal.test.tsx index 983a658fe64..96b6c0c8a4a 100644 --- a/app/src/organisms/ProtocolSetupParameters/__tests__/AnalysisFailedModal.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/AnalysisFailedModal.test.tsx @@ -4,8 +4,8 @@ import { when } from 'vitest-when' import { fireEvent, screen } from '@testing-library/react' import { useDismissCurrentRunMutation } from '@opentrons/react-api-client' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { i18n } from '../../../../../i18n' import { AnalysisFailedModal } from '../AnalysisFailedModal' import type { NavigateFunction } from 'react-router-dom' diff --git a/app/src/organisms/ProtocolSetupParameters/__tests__/ChooseCsvFile.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ChooseCsvFile.test.tsx similarity index 92% rename from app/src/organisms/ProtocolSetupParameters/__tests__/ChooseCsvFile.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ChooseCsvFile.test.tsx index 404ee1059f9..4f095a834e3 100644 --- a/app/src/organisms/ProtocolSetupParameters/__tests__/ChooseCsvFile.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ChooseCsvFile.test.tsx @@ -5,19 +5,19 @@ import { when } from 'vitest-when' import { useAllCsvFilesQuery } from '@opentrons/react-api-client' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' -import { mockConnectedRobot } from '../../../redux/discovery/__fixtures__' -import { getLocalRobot } from '../../../redux/discovery' -import { getShellUpdateDataFiles } from '../../../redux/shell' +import { i18n } from '../../../../../i18n' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { mockConnectedRobot } from '../../../../../redux/discovery/__fixtures__' +import { getLocalRobot } from '../../../../../redux/discovery' +import { getShellUpdateDataFiles } from '../../../../../redux/shell' import { EmptyFile } from '../EmptyFile' import { ChooseCsvFile } from '../ChooseCsvFile' import type { CsvFileParameter } from '@opentrons/shared-data' vi.mock('@opentrons/react-api-client') -vi.mock('../../../redux/discovery') -vi.mock('../../../redux/shell') +vi.mock('../../../../../redux/discovery') +vi.mock('../../../../../redux/shell') vi.mock('../EmptyFile') const mockHandleGoBack = vi.fn() diff --git a/app/src/organisms/ProtocolSetupParameters/__tests__/ChooseEnum.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ChooseEnum.test.tsx similarity index 94% rename from app/src/organisms/ProtocolSetupParameters/__tests__/ChooseEnum.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ChooseEnum.test.tsx index 2af4dc11a3c..fffc31e5127 100644 --- a/app/src/organisms/ProtocolSetupParameters/__tests__/ChooseEnum.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ChooseEnum.test.tsx @@ -3,11 +3,11 @@ import { it, describe, beforeEach, vi, expect } from 'vitest' import { fireEvent, screen } from '@testing-library/react' import '@testing-library/jest-dom/vitest' import { COLORS } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { i18n } from '../../../../../i18n' import { ChooseEnum } from '../ChooseEnum' -vi.mocked('../../ToasterOven') +vi.mocked('../../../../ToasterOven') const render = (props: React.ComponentProps) => { return renderWithProviders(, { i18nInstance: i18n, diff --git a/app/src/organisms/ProtocolSetupParameters/__tests__/ChooseNumber.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ChooseNumber.test.tsx similarity index 92% rename from app/src/organisms/ProtocolSetupParameters/__tests__/ChooseNumber.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ChooseNumber.test.tsx index 729a0b2df8c..56ec3669c8d 100644 --- a/app/src/organisms/ProtocolSetupParameters/__tests__/ChooseNumber.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ChooseNumber.test.tsx @@ -2,15 +2,15 @@ import * as React from 'react' import { it, describe, beforeEach, vi, expect } from 'vitest' import { fireEvent, screen } from '@testing-library/react' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { useToaster } from '../../ToasterOven' -import { mockRunTimeParameterData } from '../../../pages/ODD/ProtocolDetails/fixtures' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { i18n } from '../../../../../i18n' +import { useToaster } from '../../../../ToasterOven' +import { mockRunTimeParameterData } from '../../../../../pages/ODD/ProtocolDetails/fixtures' import { ChooseNumber } from '../ChooseNumber' import type { NumberParameter } from '@opentrons/shared-data' -vi.mock('../../ToasterOven') +vi.mock('../../../../ToasterOven') const mockHandleGoBack = vi.fn() const mockIntNumberParameterData = mockRunTimeParameterData[5] as NumberParameter diff --git a/app/src/organisms/ProtocolSetupParameters/__tests__/EmptyFile.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/EmptyFile.test.tsx similarity index 92% rename from app/src/organisms/ProtocolSetupParameters/__tests__/EmptyFile.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/EmptyFile.test.tsx index 898a38bbdf1..804eb946cfa 100644 --- a/app/src/organisms/ProtocolSetupParameters/__tests__/EmptyFile.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/EmptyFile.test.tsx @@ -11,8 +11,8 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '../../../../../i18n' +import { renderWithProviders } from '../../../../../__testing-utils__' import { EmptyFile } from '../EmptyFile' diff --git a/app/src/organisms/ProtocolSetupParameters/__tests__/ProtocolSetupParameters.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ProtocolSetupParameters.test.tsx similarity index 94% rename from app/src/organisms/ProtocolSetupParameters/__tests__/ProtocolSetupParameters.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ProtocolSetupParameters.test.tsx index e36510335cb..1b6fe5ae376 100644 --- a/app/src/organisms/ProtocolSetupParameters/__tests__/ProtocolSetupParameters.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ProtocolSetupParameters.test.tsx @@ -10,13 +10,13 @@ import { } from '@opentrons/react-api-client' import { COLORS } from '@opentrons/components' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '../../../../../i18n' +import { renderWithProviders } from '../../../../../__testing-utils__' import { ChooseEnum } from '../ChooseEnum' import { ChooseNumber } from '../ChooseNumber' import { ChooseCsvFile } from '../ChooseCsvFile' -import { mockRunTimeParameterData } from '../../../pages/ODD/ProtocolDetails/fixtures' -import { useToaster } from '../../ToasterOven' +import { mockRunTimeParameterData } from '../../../../../pages/ODD/ProtocolDetails/fixtures' +import { useToaster } from '../../../../ToasterOven' import { ProtocolSetupParameters } from '..' import type { NavigateFunction } from 'react-router-dom' @@ -28,10 +28,10 @@ const mockNavigate = vi.fn() vi.mock('../ChooseEnum') vi.mock('../ChooseNumber') vi.mock('../ChooseCsvFile') -vi.mock('../../../redux/config') -vi.mock('../../ToasterOven') +vi.mock('../../../../../redux/config') +vi.mock('../../../../ToasterOven') vi.mock('@opentrons/react-api-client') -vi.mock('../../LabwarePositionCheck/useMostRecentCompletedAnalysis') +vi.mock('../../../../LabwarePositionCheck/useMostRecentCompletedAnalysis') vi.mock('react-router-dom', async importOriginal => { const reactRouterDom = await importOriginal() return { @@ -39,7 +39,7 @@ vi.mock('react-router-dom', async importOriginal => { useNavigate: () => mockNavigate, } }) -vi.mock('../../../redux/config') +vi.mock('../../../../../redux/config') const MOCK_HOST_CONFIG: HostConfig = { hostname: 'MOCK_HOST' } const mockCreateProtocolAnalysis = vi.fn() diff --git a/app/src/organisms/ProtocolSetupParameters/__tests__/ResetValuesModal.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ResetValuesModal.test.tsx similarity index 93% rename from app/src/organisms/ProtocolSetupParameters/__tests__/ResetValuesModal.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ResetValuesModal.test.tsx index 46659717788..48d232edc5c 100644 --- a/app/src/organisms/ProtocolSetupParameters/__tests__/ResetValuesModal.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ResetValuesModal.test.tsx @@ -2,8 +2,8 @@ import * as React from 'react' import { describe, it, vi, beforeEach, expect } from 'vitest' import { fireEvent, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { i18n } from '../../../../../i18n' import { ResetValuesModal } from '../ResetValuesModal' import type { RunTimeParameter } from '@opentrons/shared-data' diff --git a/app/src/organisms/ProtocolSetupParameters/__tests__/ViewOnlyParameters.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ViewOnlyParameters.test.tsx similarity index 79% rename from app/src/organisms/ProtocolSetupParameters/__tests__/ViewOnlyParameters.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ViewOnlyParameters.test.tsx index 6a6f2be7f69..0c38e37e7ee 100644 --- a/app/src/organisms/ProtocolSetupParameters/__tests__/ViewOnlyParameters.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ViewOnlyParameters.test.tsx @@ -2,15 +2,15 @@ import * as React from 'react' import { when } from 'vitest-when' import { it, describe, beforeEach, vi, expect } from 'vitest' import { fireEvent, screen } from '@testing-library/react' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' -import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { useToaster } from '../../ToasterOven' -import { mockRunTimeParameterData } from '../../../pages/ODD/ProtocolDetails/fixtures' +import { i18n } from '../../../../../i18n' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { useMostRecentCompletedAnalysis } from '../../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' +import { useToaster } from '../../../../ToasterOven' +import { mockRunTimeParameterData } from '../../../../../pages/ODD/ProtocolDetails/fixtures' import { ViewOnlyParameters } from '../ViewOnlyParameters' -vi.mock('../../LabwarePositionCheck/useMostRecentCompletedAnalysis') -vi.mock('../../ToasterOven') +vi.mock('../../../../LabwarePositionCheck/useMostRecentCompletedAnalysis') +vi.mock('../../../../ToasterOven') const RUN_ID = 'mockId' const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/index.ts b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/index.ts new file mode 100644 index 00000000000..ecca4bd1516 --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/index.ts @@ -0,0 +1,3 @@ +export * from './ProtocolSetupParameters' +export * from './AnalysisFailedModal' +export * from './ViewOnlyParameters' diff --git a/app/src/organisms/OnDeviceDisplay/ProtocolSetup/ProtocolSetupSkeleton.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupSkeleton.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/ProtocolSetup/ProtocolSetupSkeleton.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupSkeleton.tsx diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupStep/index.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupStep/index.tsx new file mode 100644 index 00000000000..5078d53b861 --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupStep/index.tsx @@ -0,0 +1,194 @@ +import * as React from 'react' +import { css } from 'styled-components' +import { + Btn, + Flex, + Icon, + ALIGN_CENTER, + BORDERS, + DIRECTION_COLUMN, + SPACING, + JUSTIFY_END, + TEXT_ALIGN_RIGHT, + COLORS, + TYPOGRAPHY, + NO_WRAP, + LegacyStyledText, +} from '@opentrons/components' +import { useToaster } from '../../../ToasterOven' + +export type ProtocolSetupStepStatus = + | 'ready' + | 'not ready' + | 'general' + | 'inform' +interface ProtocolSetupStepProps { + onClickSetupStep: () => void + status: ProtocolSetupStepStatus + title: string + // first line of detail text + detail?: string | null + // clip detail text overflow with ellipsis + clipDetail?: boolean + // second line of detail text + subDetail?: string | null + // disallow click handler, disabled styling + disabled?: boolean + // disallow click handler, don't show CTA icons, allow styling + interactionDisabled?: boolean + // display the reason the setup step is disabled + disabledReason?: string | null + // optional description + description?: string | null + // optional removal of the left icon + hasLeftIcon?: boolean + // optional removal of the right icon + hasRightIcon?: boolean + // optional enlarge the font size + fontSize?: string +} + +export function ProtocolSetupStep({ + onClickSetupStep, + status, + title, + detail, + subDetail, + disabled = false, + clipDetail = false, + interactionDisabled = false, + disabledReason, + description, + hasRightIcon = true, + hasLeftIcon = true, + fontSize = 'p', +}: ProtocolSetupStepProps): JSX.Element { + const isInteractionDisabled = interactionDisabled || disabled + const backgroundColorByStepStatus = { + ready: COLORS.green35, + 'not ready': COLORS.yellow35, + general: COLORS.grey35, + inform: COLORS.grey35, + } + const { makeSnackbar } = useToaster() + + const makeDisabledReasonSnackbar = (): void => { + if (disabledReason != null) { + makeSnackbar(disabledReason) + } + } + + let backgroundColor: string + if (!disabled) { + switch (status) { + case 'general': + backgroundColor = COLORS.blue35 + break + case 'ready': + backgroundColor = COLORS.green40 + break + case 'inform': + backgroundColor = COLORS.grey50 + break + default: + backgroundColor = COLORS.yellow40 + } + } else backgroundColor = '' + + const PUSHED_STATE_STYLE = css` + &:active { + background-color: ${backgroundColor}; + } + ` + + const isToggle = detail === 'On' || detail === 'Off' + + return ( + { + !isInteractionDisabled + ? onClickSetupStep() + : makeDisabledReasonSnackbar() + }} + width="100%" + data-testid={`SetupButton_${title}`} + > + + {status !== 'general' && + !disabled && + status !== 'inform' && + hasLeftIcon ? ( + + ) : null} + + + {title} + + {description != null ? ( + + {description} + + ) : null} + + + + {detail} + {subDetail != null && detail != null ?
: null} + {subDetail} +
+
+ {interactionDisabled || !hasRightIcon ? null : ( + + )} +
+
+ ) +} + +const CLIPPED_TEXT_STYLE = css` + white-space: ${NO_WRAP}; + overflow: hidden; + text-overflow: ellipsis; +` diff --git a/app/src/organisms/OnDeviceDisplay/ProtocolSetup/__tests__/ProtocolSetupSkeleton.test.tsx b/app/src/organisms/ODD/ProtocolSetup/__tests__/ProtocolSetupSkeleton.test.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/ProtocolSetup/__tests__/ProtocolSetupSkeleton.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/__tests__/ProtocolSetupSkeleton.test.tsx diff --git a/app/src/organisms/ODD/ProtocolSetup/index.ts b/app/src/organisms/ODD/ProtocolSetup/index.ts new file mode 100644 index 00000000000..74473b8146b --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/index.ts @@ -0,0 +1,11 @@ +export * from './ProtocolSetupDeckConfiguration' +export * from './ProtocolSetupInstruments' +export * from './ProtocolSetupLabware' +export * from './ProtocolSetupLiquids' +export * from './ProtocolSetupModulesAndDeck' +export * from './ProtocolSetupOffsets' +export * from './ProtocolSetupParameters' +export * from './ProtocolSetupSkeleton' +export * from './ProtocolSetupStep' + +export type * from './types' diff --git a/app/src/organisms/ODD/ProtocolSetup/types.ts b/app/src/organisms/ODD/ProtocolSetup/types.ts new file mode 100644 index 00000000000..01d704da417 --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/types.ts @@ -0,0 +1,9 @@ +export type SetupScreens = + | 'prepare to run' + | 'instruments' + | 'modules' + | 'offsets' + | 'labware' + | 'liquids' + | 'deck configuration' + | 'view only parameters' diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/EmptyRecentRun.tsx b/app/src/organisms/ODD/RobotDashboard/EmptyRecentRun.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/EmptyRecentRun.tsx rename to app/src/organisms/ODD/RobotDashboard/EmptyRecentRun.tsx diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/RecentRunProtocolCard.tsx b/app/src/organisms/ODD/RobotDashboard/RecentRunProtocolCard.tsx similarity index 97% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/RecentRunProtocolCard.tsx rename to app/src/organisms/ODD/RobotDashboard/RecentRunProtocolCard.tsx index 3fee3a1f9c0..cc735256244 100644 --- a/app/src/organisms/OnDeviceDisplay/RobotDashboard/RecentRunProtocolCard.tsx +++ b/app/src/organisms/ODD/RobotDashboard/RecentRunProtocolCard.tsx @@ -28,13 +28,13 @@ import { RUN_STATUS_SUCCEEDED, } from '@opentrons/api-client' -import { ODD_FOCUS_VISIBLE } from '../../../atoms/buttons//constants' +import { ODD_FOCUS_VISIBLE } from '../../../atoms/buttons/constants' import { useTrackEvent, ANALYTICS_PROTOCOL_PROCEED_TO_RUN, } from '../../../redux/analytics' import { Skeleton } from '../../../atoms/Skeleton' -import { useMissingProtocolHardware } from '../../../pages/Desktop/Protocols/hooks' +import { useMissingProtocolHardware } from '../../../transformations/commands' import { useCloneRun } from '../../ProtocolUpload/hooks' import { useRerunnableStatusText } from './hooks' diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/RecentRunProtocolCarousel.tsx b/app/src/organisms/ODD/RobotDashboard/RecentRunProtocolCarousel.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/RecentRunProtocolCarousel.tsx rename to app/src/organisms/ODD/RobotDashboard/RecentRunProtocolCarousel.tsx diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/ServerInitializing.tsx b/app/src/organisms/ODD/RobotDashboard/ServerInitializing.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/ServerInitializing.tsx rename to app/src/organisms/ODD/RobotDashboard/ServerInitializing.tsx diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/__tests__/EmptyRecentRun.test.tsx b/app/src/organisms/ODD/RobotDashboard/__tests__/EmptyRecentRun.test.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/__tests__/EmptyRecentRun.test.tsx rename to app/src/organisms/ODD/RobotDashboard/__tests__/EmptyRecentRun.test.tsx diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/__tests__/RecentRunProtocolCard.test.tsx b/app/src/organisms/ODD/RobotDashboard/__tests__/RecentRunProtocolCard.test.tsx similarity index 97% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/__tests__/RecentRunProtocolCard.test.tsx rename to app/src/organisms/ODD/RobotDashboard/__tests__/RecentRunProtocolCard.test.tsx index c97a94b8cff..967b44ce21e 100644 --- a/app/src/organisms/OnDeviceDisplay/RobotDashboard/__tests__/RecentRunProtocolCard.test.tsx +++ b/app/src/organisms/ODD/RobotDashboard/__tests__/RecentRunProtocolCard.test.tsx @@ -16,7 +16,7 @@ import { simpleAnalysisFileFixture } from '@opentrons/shared-data' import { renderWithProviders } from '../../../../__testing-utils__' import { i18n } from '../../../../i18n' import { Skeleton } from '../../../../atoms/Skeleton' -import { useMissingProtocolHardware } from '../../../../pages/Desktop/Protocols/hooks' +import { useMissingProtocolHardware } from '../../../../transformations/commands' import { useTrackProtocolRunEvent } from '../../../Devices/hooks' import { useTrackEvent, @@ -28,7 +28,7 @@ import { RecentRunProtocolCard } from '../' import { useNotifyAllRunsQuery } from '../../../../resources/runs' import type { NavigateFunction } from 'react-router-dom' -import type { ProtocolHardware } from '../../../../pages/Desktop/Protocols/hooks' +import type { ProtocolHardware } from '../../../../transformations/commands' const mockNavigate = vi.fn() @@ -42,7 +42,7 @@ vi.mock('react-router-dom', async importOriginal => { vi.mock('@opentrons/react-api-client') vi.mock('../../../../atoms/Skeleton') -vi.mock('../../../../pages/Desktop/Protocols/hooks') +vi.mock('../../../../transformations/commands') vi.mock('../../../../pages/ODD/ProtocolDetails') vi.mock('../../../../organisms/Devices/hooks') vi.mock('../../../../organisms/RunTimeControl/hooks') diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/__tests__/RecentRunProtocolCarousel.test.tsx b/app/src/organisms/ODD/RobotDashboard/__tests__/RecentRunProtocolCarousel.test.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/__tests__/RecentRunProtocolCarousel.test.tsx rename to app/src/organisms/ODD/RobotDashboard/__tests__/RecentRunProtocolCarousel.test.tsx diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/__tests__/useHardwareStatusText.test.tsx b/app/src/organisms/ODD/RobotDashboard/hooks/__tests__/useHardwareStatusText.test.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/__tests__/useHardwareStatusText.test.tsx rename to app/src/organisms/ODD/RobotDashboard/hooks/__tests__/useHardwareStatusText.test.tsx diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/index.ts b/app/src/organisms/ODD/RobotDashboard/hooks/index.ts similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/index.ts rename to app/src/organisms/ODD/RobotDashboard/hooks/index.ts diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/useHardwareStatusText.ts b/app/src/organisms/ODD/RobotDashboard/hooks/useHardwareStatusText.ts similarity index 94% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/useHardwareStatusText.ts rename to app/src/organisms/ODD/RobotDashboard/hooks/useHardwareStatusText.ts index 717f76c2832..0f285e2c24e 100644 --- a/app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/useHardwareStatusText.ts +++ b/app/src/organisms/ODD/RobotDashboard/hooks/useHardwareStatusText.ts @@ -1,6 +1,6 @@ import { useTranslation } from 'react-i18next' -import type { ProtocolHardware } from '../../../../pages/Desktop/Protocols/hooks' +import type { ProtocolHardware } from '../../../../transformations/commands' export function useHardwareStatusText( missingProtocolHardware: ProtocolHardware[], diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/useRerunnableStatusText.ts b/app/src/organisms/ODD/RobotDashboard/hooks/useRerunnableStatusText.ts similarity index 85% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/useRerunnableStatusText.ts rename to app/src/organisms/ODD/RobotDashboard/hooks/useRerunnableStatusText.ts index eda6abac73d..e9d8353f95f 100644 --- a/app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/useRerunnableStatusText.ts +++ b/app/src/organisms/ODD/RobotDashboard/hooks/useRerunnableStatusText.ts @@ -1,6 +1,6 @@ import { useTranslation } from 'react-i18next' import { useHardwareStatusText } from './useHardwareStatusText' -import type { ProtocolHardware } from '../../../../pages/Desktop/Protocols/hooks' +import type { ProtocolHardware } from '../../../../transformations/commands' export function useRerunnableStatusText( runOk: boolean, diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/index.ts b/app/src/organisms/ODD/RobotDashboard/index.ts similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/index.ts rename to app/src/organisms/ODD/RobotDashboard/index.ts diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/CancelingRunModal.tsx b/app/src/organisms/ODD/RunningProtocol/CancelingRunModal.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/CancelingRunModal.tsx rename to app/src/organisms/ODD/RunningProtocol/CancelingRunModal.tsx diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/ConfirmCancelRunModal.tsx b/app/src/organisms/ODD/RunningProtocol/ConfirmCancelRunModal.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/ConfirmCancelRunModal.tsx rename to app/src/organisms/ODD/RunningProtocol/ConfirmCancelRunModal.tsx diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/CurrentRunningProtocolCommand.tsx b/app/src/organisms/ODD/RunningProtocol/CurrentRunningProtocolCommand.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/CurrentRunningProtocolCommand.tsx rename to app/src/organisms/ODD/RunningProtocol/CurrentRunningProtocolCommand.tsx diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/PlayPauseButton.tsx b/app/src/organisms/ODD/RunningProtocol/PlayPauseButton.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/PlayPauseButton.tsx rename to app/src/organisms/ODD/RunningProtocol/PlayPauseButton.tsx diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/RunFailedModal.tsx b/app/src/organisms/ODD/RunningProtocol/RunFailedModal.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/RunFailedModal.tsx rename to app/src/organisms/ODD/RunningProtocol/RunFailedModal.tsx diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/RunningProtocolCommandList.tsx b/app/src/organisms/ODD/RunningProtocol/RunningProtocolCommandList.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/RunningProtocolCommandList.tsx rename to app/src/organisms/ODD/RunningProtocol/RunningProtocolCommandList.tsx diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/RunningProtocolSkeleton.tsx b/app/src/organisms/ODD/RunningProtocol/RunningProtocolSkeleton.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/RunningProtocolSkeleton.tsx rename to app/src/organisms/ODD/RunningProtocol/RunningProtocolSkeleton.tsx diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/StopButton.tsx b/app/src/organisms/ODD/RunningProtocol/StopButton.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/StopButton.tsx rename to app/src/organisms/ODD/RunningProtocol/StopButton.tsx diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/CancelingRunModal.test.tsx b/app/src/organisms/ODD/RunningProtocol/__tests__/CancelingRunModal.test.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/CancelingRunModal.test.tsx rename to app/src/organisms/ODD/RunningProtocol/__tests__/CancelingRunModal.test.tsx diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/ConfirmCancelRunModal.test.tsx b/app/src/organisms/ODD/RunningProtocol/__tests__/ConfirmCancelRunModal.test.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/ConfirmCancelRunModal.test.tsx rename to app/src/organisms/ODD/RunningProtocol/__tests__/ConfirmCancelRunModal.test.tsx diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/CurrentRunningProtocolCommand.test.tsx b/app/src/organisms/ODD/RunningProtocol/__tests__/CurrentRunningProtocolCommand.test.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/CurrentRunningProtocolCommand.test.tsx rename to app/src/organisms/ODD/RunningProtocol/__tests__/CurrentRunningProtocolCommand.test.tsx diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/RunFailedModal.test.tsx b/app/src/organisms/ODD/RunningProtocol/__tests__/RunFailedModal.test.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/RunFailedModal.test.tsx rename to app/src/organisms/ODD/RunningProtocol/__tests__/RunFailedModal.test.tsx diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/RunningProtocolCommandList.test.tsx b/app/src/organisms/ODD/RunningProtocol/__tests__/RunningProtocolCommandList.test.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/RunningProtocolCommandList.test.tsx rename to app/src/organisms/ODD/RunningProtocol/__tests__/RunningProtocolCommandList.test.tsx diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/RunningProtocolSkeleton.test.tsx b/app/src/organisms/ODD/RunningProtocol/__tests__/RunningProtocolSkeleton.test.tsx similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/RunningProtocolSkeleton.test.tsx rename to app/src/organisms/ODD/RunningProtocol/__tests__/RunningProtocolSkeleton.test.tsx diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/index.ts b/app/src/organisms/ODD/RunningProtocol/index.ts similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/index.ts rename to app/src/organisms/ODD/RunningProtocol/index.ts diff --git a/app/src/organisms/OnDeviceDisplay/ProtocolSetup/index.ts b/app/src/organisms/OnDeviceDisplay/ProtocolSetup/index.ts deleted file mode 100644 index 763d2d63602..00000000000 --- a/app/src/organisms/OnDeviceDisplay/ProtocolSetup/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ProtocolSetupSkeleton' diff --git a/app/src/organisms/ProtocolDetails/index.tsx b/app/src/organisms/ProtocolDetails/index.tsx index c5d927ba73d..dda6e06d4a4 100644 --- a/app/src/organisms/ProtocolDetails/index.tsx +++ b/app/src/organisms/ProtocolDetails/index.tsx @@ -67,7 +67,7 @@ import { getAnalysisStatus, getProtocolDisplayName, } from '../ProtocolsLanding/utils' -import { getProtocolUsesGripper } from '../ProtocolSetupInstruments/utils' +import { getProtocolUsesGripper } from '../../transformations/commands' import { ProtocolOverflowMenu } from '../ProtocolsLanding/ProtocolOverflowMenu' import { ProtocolStats } from './ProtocolStats' import { ProtocolLabwareDetails } from './ProtocolLabwareDetails' diff --git a/app/src/organisms/ProtocolsLanding/ProtocolCard.tsx b/app/src/organisms/ProtocolsLanding/ProtocolCard.tsx index e4965c7b96c..55e48479245 100644 --- a/app/src/organisms/ProtocolsLanding/ProtocolCard.tsx +++ b/app/src/organisms/ProtocolsLanding/ProtocolCard.tsx @@ -39,7 +39,7 @@ import { InstrumentContainer } from '../../atoms/InstrumentContainer' import { ProtocolOverflowMenu } from './ProtocolOverflowMenu' import { ProtocolAnalysisFailure } from '../ProtocolAnalysisFailure' import { ProtocolStatusBanner } from '../ProtocolStatusBanner' -import { getProtocolUsesGripper } from '../ProtocolSetupInstruments/utils' +import { getProtocolUsesGripper } from '../../transformations/commands' import { ProtocolAnalysisStale } from '../ProtocolAnalysisFailure/ProtocolAnalysisStale' import { getAnalysisStatus, diff --git a/app/src/pages/Desktop/Protocols/hooks/__tests__/hooks.test.tsx b/app/src/pages/Desktop/Protocols/hooks/__tests__/hooks.test.tsx index 4a0bfed44fd..b72b60bd77f 100644 --- a/app/src/pages/Desktop/Protocols/hooks/__tests__/hooks.test.tsx +++ b/app/src/pages/Desktop/Protocols/hooks/__tests__/hooks.test.tsx @@ -1,41 +1,23 @@ import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' import { renderHook } from '@testing-library/react' import { when } from 'vitest-when' -import omitBy from 'lodash/omitBy' import { useProtocolQuery, useProtocolAnalysisAsDocumentQuery, - useInstrumentsQuery, - useModulesQuery, } from '@opentrons/react-api-client' -import { - FLEX_SIMPLEST_DECK_CONFIG, - WASTE_CHUTE_RIGHT_ADAPTER_NO_COVER_FIXTURE, - fixtureTiprack300ul, -} from '@opentrons/shared-data' -import { - useMissingProtocolHardware, - useRequiredProtocolLabware, - useRunTimeParameters, -} from '../index' -import { useNotifyDeckConfigurationQuery } from '../../../../../resources/deck_configuration/useNotifyDeckConfigurationQuery' -import { mockHeaterShaker } from '../../../../../redux/modules/__fixtures__' +import { fixtureTiprack300ul } from '@opentrons/shared-data' +import { useRequiredProtocolLabware, useRunTimeParameters } from '../index' import type { UseQueryResult } from 'react-query' import type { CompletedProtocolAnalysis, - DeckConfiguration, LabwareDefinition2, } from '@opentrons/shared-data' import type { Protocol } from '@opentrons/api-client' vi.mock('@opentrons/react-api-client') vi.mock('../../../../../organisms/Devices/hooks') -vi.mock('../../../../../redux/config') -vi.mock( - '../../../../../resources/deck_configuration/useNotifyDeckConfigurationQuery' -) const PROTOCOL_ID = 'fake_protocol_id' const mockRTPData = [ @@ -265,187 +247,3 @@ describe('useRequiredProtocolLabware', () => { expect(result.current.length).toBe(0) }) }) - -describe.only('useMissingProtocolHardware', () => { - let wrapper: React.FunctionComponent<{ children: React.ReactNode }> - beforeEach(() => { - vi.mocked(useInstrumentsQuery).mockReturnValue({ - data: { data: [] }, - isLoading: false, - } as any) - vi.mocked(useModulesQuery).mockReturnValue({ - data: { data: [] }, - isLoading: false, - } as any) - vi.mocked(useProtocolQuery).mockReturnValue({ - data: { - data: { analysisSummaries: [{ id: PROTOCOL_ANALYSIS.id } as any] }, - }, - } as UseQueryResult) - vi.mocked(useProtocolAnalysisAsDocumentQuery).mockReturnValue({ - data: PROTOCOL_ANALYSIS, - } as UseQueryResult) - vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ - data: [{}], - } as UseQueryResult) - }) - - afterEach(() => { - vi.resetAllMocks() - }) - it('should return 1 pipette and 1 module', () => { - const { result } = renderHook( - () => useMissingProtocolHardware(PROTOCOL_ANALYSIS.id), - { wrapper } - ) - expect(result.current).toEqual({ - isLoading: false, - missingProtocolHardware: [ - { - hardwareType: 'pipette', - pipetteName: 'p1000_multi_flex', - mount: 'left', - connected: false, - }, - { - hardwareType: 'module', - moduleModel: 'heaterShakerModuleV1', - slot: 'D3', - connected: false, - hasSlotConflict: false, - }, - ], - conflictedSlots: [], - }) - }) - it('should return 1 conflicted slot', () => { - vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue(({ - data: [ - { - cutoutId: 'cutoutD3', - cutoutFixtureId: WASTE_CHUTE_RIGHT_ADAPTER_NO_COVER_FIXTURE, - }, - ], - } as any) as UseQueryResult) - - const { result } = renderHook( - () => useMissingProtocolHardware(PROTOCOL_ANALYSIS.id), - { wrapper } - ) - expect(result.current).toEqual({ - isLoading: false, - missingProtocolHardware: [ - { - hardwareType: 'pipette', - pipetteName: 'p1000_multi_flex', - mount: 'left', - connected: false, - }, - { - hardwareType: 'module', - moduleModel: 'heaterShakerModuleV1', - slot: 'D3', - connected: false, - hasSlotConflict: true, - }, - ], - conflictedSlots: ['D3'], - }) - }) - it('should return empty array when the correct modules and pipettes are attached', () => { - vi.mocked(useInstrumentsQuery).mockReturnValue({ - data: { - data: [ - { - mount: 'left', - instrumentType: 'pipette', - instrumentName: 'p1000_multi_flex', - ok: true, - }, - ], - }, - isLoading: false, - } as any) - - vi.mocked(useModulesQuery).mockReturnValue({ - data: { data: [mockHeaterShaker] }, - isLoading: false, - } as any) - vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ - data: [ - omitBy( - FLEX_SIMPLEST_DECK_CONFIG, - ({ cutoutId }) => cutoutId === 'cutoutD3' - ), - { - cutoutId: 'cutoutD3', - cutoutFixtureId: 'heaterShakerModuleV1', - opentronsModuleSerialNumber: mockHeaterShaker.serialNumber, - }, - ], - isLoading: false, - } as any) - - const { result } = renderHook( - () => useMissingProtocolHardware(PROTOCOL_ANALYSIS.id), - { wrapper } - ) - expect(result.current).toEqual({ - missingProtocolHardware: [], - isLoading: false, - conflictedSlots: [], - }) - }) - it('should return conflicting slot when module location is configured with something other than module fixture', () => { - vi.mocked(useInstrumentsQuery).mockReturnValue({ - data: { - data: [ - { - mount: 'left', - instrumentType: 'pipette', - instrumentName: 'p1000_multi_flex', - ok: true, - }, - ], - }, - isLoading: false, - } as any) - - vi.mocked(useModulesQuery).mockReturnValue({ - data: { data: [mockHeaterShaker] }, - isLoading: false, - } as any) - - vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ - data: [ - omitBy( - FLEX_SIMPLEST_DECK_CONFIG, - ({ cutoutId }) => cutoutId === 'cutoutD3' - ), - { - cutoutId: 'cutoutD3', - cutoutFixtureId: WASTE_CHUTE_RIGHT_ADAPTER_NO_COVER_FIXTURE, - }, - ], - isLoading: false, - } as any) - - const { result } = renderHook( - () => useMissingProtocolHardware(PROTOCOL_ANALYSIS.id), - { wrapper } - ) - expect(result.current).toEqual({ - missingProtocolHardware: [ - { - hardwareType: 'module', - moduleModel: 'heaterShakerModuleV1', - slot: 'D3', - connected: false, - hasSlotConflict: true, - }, - ], - isLoading: false, - conflictedSlots: ['D3'], - }) - }) -}) diff --git a/app/src/pages/Desktop/Protocols/hooks/index.ts b/app/src/pages/Desktop/Protocols/hooks/index.ts index c5631edf99f..26941e94acd 100644 --- a/app/src/pages/Desktop/Protocols/hooks/index.ts +++ b/app/src/pages/Desktop/Protocols/hooks/index.ts @@ -1,209 +1,22 @@ import last from 'lodash/last' import { - useInstrumentsQuery, - useModulesQuery, useProtocolAnalysisAsDocumentQuery, useProtocolQuery, } from '@opentrons/react-api-client' + import { - FLEX_ROBOT_TYPE, - FLEX_SINGLE_SLOT_ADDRESSABLE_AREAS, - getCutoutIdForSlotName, - getDeckDefFromRobotType, - getCutoutFixtureIdsForModuleModel, - getCutoutFixturesForModuleModel, - FLEX_MODULE_ADDRESSABLE_AREAS, - getModuleType, - FLEX_USB_MODULE_ADDRESSABLE_AREAS, - MAGNETIC_BLOCK_TYPE, -} from '@opentrons/shared-data' -import { getLabwareSetupItemGroups } from '../utils' -import { getProtocolUsesGripper } from '../../../../organisms/ProtocolSetupInstruments/utils' -import { useDeckConfigurationCompatibility } from '../../../../resources/deck_configuration/hooks' -import { useNotifyDeckConfigurationQuery } from '../../../../resources/deck_configuration' + getLabwareSetupItemGroups, + useRequiredProtocolHardwareFromAnalysis, +} from '../../../../transformations/commands' import type { CompletedProtocolAnalysis, - CutoutFixtureId, - CutoutId, - ModuleModel, - PipetteName, - ProtocolAnalysisOutput, - RobotType, RunTimeParameter, } from '@opentrons/shared-data' -import type { LabwareSetupItem } from '../utils' - -export interface ProtocolPipette { - hardwareType: 'pipette' - pipetteName: PipetteName - mount: 'left' | 'right' - connected: boolean -} - -interface ProtocolModule { - hardwareType: 'module' - moduleModel: ModuleModel - slot: string - connected: boolean - hasSlotConflict: boolean -} - -interface ProtocolGripper { - hardwareType: 'gripper' - connected: boolean -} - -export interface ProtocolFixture { - hardwareType: 'fixture' - cutoutFixtureId: CutoutFixtureId | null - location: { cutout: CutoutId } - hasSlotConflict: boolean -} - -export type ProtocolHardware = - | ProtocolPipette - | ProtocolModule - | ProtocolGripper - | ProtocolFixture - -const DECK_CONFIG_REFETCH_INTERVAL = 5000 - -export const useRequiredProtocolHardwareFromAnalysis = ( - analysis: CompletedProtocolAnalysis | null -): { requiredProtocolHardware: ProtocolHardware[]; isLoading: boolean } => { - const { - data: attachedModulesData, - isLoading: isLoadingModules, - } = useModulesQuery() - const attachedModules = attachedModulesData?.data ?? [] - - const { - data: attachedInstrumentsData, - isLoading: isLoadingInstruments, - } = useInstrumentsQuery() - const attachedInstruments = attachedInstrumentsData?.data ?? [] - - const robotType = FLEX_ROBOT_TYPE - const deckDef = getDeckDefFromRobotType(robotType) - const deckConfig = - useNotifyDeckConfigurationQuery({ - refetchInterval: DECK_CONFIG_REFETCH_INTERVAL, - })?.data ?? [] - const deckConfigCompatibility = useDeckConfigurationCompatibility( - robotType, - analysis - ) - - if (analysis == null || analysis?.status !== 'completed') { - return { requiredProtocolHardware: [], isLoading: true } - } - - const requiredGripper: ProtocolGripper[] = getProtocolUsesGripper(analysis) - ? [ - { - hardwareType: 'gripper', - connected: - attachedInstruments.some(i => i.instrumentType === 'gripper') ?? - false, - }, - ] - : [] - - const requiredModules: ProtocolModule[] = analysis.modules - // remove magnetic blocks, they're handled by required fixtures - .filter(m => getModuleType(m.model) !== MAGNETIC_BLOCK_TYPE) - .map(({ location, model }) => { - const cutoutIdForSlotName = getCutoutIdForSlotName( - location.slotName, - deckDef - ) - const moduleFixtures = getCutoutFixturesForModuleModel(model, deckDef) - - const configuredModuleSerialNumber = - deckConfig.find( - ({ cutoutId, cutoutFixtureId }) => - cutoutId === cutoutIdForSlotName && - moduleFixtures.map(mf => mf.id).includes(cutoutFixtureId) - )?.opentronsModuleSerialNumber ?? null - const isConnected = moduleFixtures.every( - mf => mf.expectOpentronsModuleSerialNumber - ) - ? attachedModules.some( - m => - m.moduleModel === model && - m.serialNumber === configuredModuleSerialNumber - ) - : true - return { - hardwareType: 'module', - moduleModel: model, - slot: location.slotName, - connected: isConnected, - hasSlotConflict: deckConfig.some( - ({ cutoutId, cutoutFixtureId }) => - cutoutId === getCutoutIdForSlotName(location.slotName, deckDef) && - !getCutoutFixtureIdsForModuleModel(model).includes(cutoutFixtureId) - ), - } - }) - - const requiredPipettes: ProtocolPipette[] = analysis.pipettes.map( - ({ mount, pipetteName }) => ({ - hardwareType: 'pipette', - pipetteName: pipetteName, - mount: mount, - connected: - attachedInstruments.some( - i => - i.instrumentType === 'pipette' && - i.ok && - i.mount === mount && - i.instrumentName === pipetteName - ) ?? false, - }) - ) - - // fixture includes at least 1 required addressableArea AND it doesn't ONLY include a single slot addressableArea - const requiredDeckConfigCompatibility = deckConfigCompatibility.filter( - ({ requiredAddressableAreas }) => { - const atLeastOneAA = requiredAddressableAreas.length > 0 - const notOnlySingleSlot = !( - requiredAddressableAreas.length === 1 && - FLEX_SINGLE_SLOT_ADDRESSABLE_AREAS.includes(requiredAddressableAreas[0]) - ) - return atLeastOneAA && notOnlySingleSlot - } - ) - - const requiredFixtures = requiredDeckConfigCompatibility - // filter out all fixtures that only provide usb module addressable areas - // as they're handled in the requiredModules section via hardwareType === 'module' - .filter( - ({ requiredAddressableAreas }) => - !requiredAddressableAreas.every(modAA => - FLEX_USB_MODULE_ADDRESSABLE_AREAS.includes(modAA) - ) - ) - .map(({ cutoutFixtureId, cutoutId, compatibleCutoutFixtureIds }) => ({ - hardwareType: 'fixture' as const, - cutoutFixtureId: compatibleCutoutFixtureIds[0], - location: { cutout: cutoutId }, - hasSlotConflict: - cutoutFixtureId != null && - !compatibleCutoutFixtureIds.includes(cutoutFixtureId), - })) - - return { - requiredProtocolHardware: [ - ...requiredPipettes, - ...requiredModules, - ...requiredGripper, - ...requiredFixtures, - ], - isLoading: isLoadingInstruments || isLoadingModules, - } -} +import type { + LabwareSetupItem, + ProtocolHardware, +} from '../../../../transformations/commands' /** * Returns an array of RunTimeParameters objects that are optional by the given protocol ID. @@ -267,116 +80,3 @@ export const useRequiredProtocolLabware = ( const { onDeckItems, offDeckItems } = getLabwareSetupItemGroups(commands) return [...onDeckItems, ...offDeckItems] } - -/** - * Returns an array of ProtocolHardware objects that are required by the given protocol ID, - * but not currently connected. - * - * @param {ProtocolHardware[]} requiredProtocolHardware An array of ProtocolHardware objects that are required by a protocol. - * @param {boolean} isLoading A boolean determining whether any required protocol hardware is loading. - * @returns {ProtocolHardware[]} An array of ProtocolHardware objects that are required by the given protocol ID, but not currently connected. - */ - -const useMissingProtocolHardwareFromRequiredProtocolHardware = ( - requiredProtocolHardware: ProtocolHardware[], - isLoading: boolean, - robotType: RobotType, - protocolAnalysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput | null -): { - missingProtocolHardware: ProtocolHardware[] - conflictedSlots: string[] - isLoading: boolean -} => { - const deckConfigCompatibility = useDeckConfigurationCompatibility( - robotType, - protocolAnalysis - ) - // determine missing or conflicted hardware - return { - missingProtocolHardware: [ - ...requiredProtocolHardware.filter( - hardware => 'connected' in hardware && !hardware.connected - ), - ...deckConfigCompatibility - .filter( - ({ - cutoutFixtureId, - compatibleCutoutFixtureIds, - requiredAddressableAreas, - }) => - cutoutFixtureId != null && - !compatibleCutoutFixtureIds.some(id => id === cutoutFixtureId) && - !FLEX_MODULE_ADDRESSABLE_AREAS.some(modAA => - requiredAddressableAreas.includes(modAA) - ) // modules are already included via requiredProtocolHardware - ) - .map(({ compatibleCutoutFixtureIds, cutoutId }) => ({ - hardwareType: 'fixture' as const, - cutoutFixtureId: compatibleCutoutFixtureIds[0], - location: { cutout: cutoutId }, - hasSlotConflict: true, - })), - ], - conflictedSlots: requiredProtocolHardware - .filter( - (hardware): hardware is ProtocolModule | ProtocolFixture => - (hardware.hardwareType === 'module' || - hardware.hardwareType === 'fixture') && - hardware.hasSlotConflict - ) - .map( - hardware => - hardware.hardwareType === 'module' - ? hardware.slot // module - : hardware.location.cutout // fixture - ), - isLoading, - } -} - -export const useMissingProtocolHardwareFromAnalysis = ( - robotType: RobotType, - analysis: CompletedProtocolAnalysis | null -): { - missingProtocolHardware: ProtocolHardware[] - conflictedSlots: string[] - isLoading: boolean -} => { - const { - requiredProtocolHardware, - isLoading, - } = useRequiredProtocolHardwareFromAnalysis(analysis) - - return useMissingProtocolHardwareFromRequiredProtocolHardware( - requiredProtocolHardware, - isLoading, - robotType, - analysis ?? null - ) -} - -export const useMissingProtocolHardware = ( - protocolId: string -): { - missingProtocolHardware: ProtocolHardware[] - conflictedSlots: string[] - isLoading: boolean -} => { - const { data: protocolData } = useProtocolQuery(protocolId) - const { data: analysis } = useProtocolAnalysisAsDocumentQuery( - protocolId, - last(protocolData?.data.analysisSummaries)?.id ?? null, - { enabled: protocolData != null } - ) - const { - requiredProtocolHardware, - isLoading, - } = useRequiredProtocolHardwareFromAnalysis(analysis ?? null) - - return useMissingProtocolHardwareFromRequiredProtocolHardware( - requiredProtocolHardware, - isLoading, - FLEX_ROBOT_TYPE, - analysis ?? null - ) -} diff --git a/app/src/pages/ODD/NameRobot/index.tsx b/app/src/pages/ODD/NameRobot/index.tsx index deedf294e0c..4f0660b6384 100644 --- a/app/src/pages/ODD/NameRobot/index.tsx +++ b/app/src/pages/ODD/NameRobot/index.tsx @@ -36,7 +36,7 @@ import { AlphanumericKeyboard } from '../../../atoms/SoftwareKeyboard' import { SmallButton } from '../../../atoms/buttons' import { StepMeter } from '../../../atoms/StepMeter' import { useIsUnboxingFlowOngoing } from '../../../organisms/RobotSettingsDashboard/NetworkSettings/hooks' -import { ConfirmRobotName } from '../../../organisms/OnDeviceDisplay/NameRobot/ConfirmRobotName' +import { ConfirmRobotName } from '../../../organisms/ODD/NameRobot/ConfirmRobotName' import type { FieldError, Resolver } from 'react-hook-form' import type { UpdatedRobotName } from '@opentrons/api-client' diff --git a/app/src/pages/ODD/ProtocolDetails/Deck.tsx b/app/src/pages/ODD/ProtocolDetails/Deck.tsx index 0afe8a1c2ef..6dc1a5e95bf 100644 --- a/app/src/pages/ODD/ProtocolDetails/Deck.tsx +++ b/app/src/pages/ODD/ProtocolDetails/Deck.tsx @@ -9,8 +9,8 @@ import { import { getLabwareDefURI } from '@opentrons/shared-data' import { LabwareStackModal } from '../../../organisms/Devices/ProtocolRun/SetupLabware/LabwareStackModal' -import { SingleLabwareModal } from '../../../organisms/ProtocolSetupLabware/SingleLabwareModal' -import { getLabwareSetupItemGroups } from '../../../pages/Desktop/Protocols/utils' +import { SingleLabwareModal } from '../../../organisms/ODD/ProtocolSetup/ProtocolSetupLabware/SingleLabwareModal' +import { getLabwareSetupItemGroups } from '../../../transformations/commands' import type { LabwareDefinition2, diff --git a/app/src/pages/ODD/ProtocolDetails/Hardware.tsx b/app/src/pages/ODD/ProtocolDetails/Hardware.tsx index e446b44270b..66adc22594c 100644 --- a/app/src/pages/ODD/ProtocolDetails/Hardware.tsx +++ b/app/src/pages/ODD/ProtocolDetails/Hardware.tsx @@ -36,7 +36,7 @@ import type { TFunction } from 'i18next' import type { ProtocolHardware, ProtocolPipette, -} from '../../../pages/Desktop/Protocols/hooks' +} from '../../../transformations/commands' const Table = styled('table')` ${TYPOGRAPHY.labelRegular} diff --git a/app/src/pages/ODD/ProtocolDetails/__tests__/ProtocolDetails.test.tsx b/app/src/pages/ODD/ProtocolDetails/__tests__/ProtocolDetails.test.tsx index f7939d44e13..9f48408c6be 100644 --- a/app/src/pages/ODD/ProtocolDetails/__tests__/ProtocolDetails.test.tsx +++ b/app/src/pages/ODD/ProtocolDetails/__tests__/ProtocolDetails.test.tsx @@ -13,14 +13,12 @@ import { useProtocolAnalysisAsDocumentQuery, } from '@opentrons/react-api-client' import { i18n } from '../../../../i18n' -import { useHardwareStatusText } from '../../../../organisms/OnDeviceDisplay/RobotDashboard/hooks' +import { useHardwareStatusText } from '../../../../organisms/ODD/RobotDashboard/hooks' import { useOffsetCandidatesForAnalysis } from '../../../../organisms/ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' -import { - useMissingProtocolHardware, - useRunTimeParameters, -} from '../../../../pages/Desktop/Protocols/hooks' -import { ProtocolSetupParameters } from '../../../../organisms/ProtocolSetupParameters' +import { useRunTimeParameters } from '../../../../pages/Desktop/Protocols/hooks' +import { ProtocolSetupParameters } from '../../../../organisms/ODD/ProtocolSetup/ProtocolSetupParameters' import { formatTimeWithUtcLabel } from '../../../../resources/runs' +import { useMissingProtocolHardware } from '../../../../transformations/commands' import { ProtocolDetails } from '..' import { Deck } from '../Deck' import { Hardware } from '../Hardware' @@ -42,14 +40,17 @@ Object.defineProperty(window, 'IntersectionObserver', { configurable: true, value: IntersectionObserver, }) -vi.mock('../../../../organisms/ProtocolSetupParameters') +vi.mock( + '../../../../organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ProtocolSetupParameters' +) vi.mock('@opentrons/api-client') vi.mock('@opentrons/react-api-client') -vi.mock('../../../../organisms/OnDeviceDisplay/RobotDashboard/hooks') +vi.mock('../../../../organisms/ODD/RobotDashboard/hooks') vi.mock( '../../../../organisms/ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' ) vi.mock('../../../../pages/Desktop/Protocols/hooks') +vi.mock('../../../../transformations/commands') vi.mock('../Deck') vi.mock('../Hardware') vi.mock('../Labware') diff --git a/app/src/pages/ODD/ProtocolDetails/index.tsx b/app/src/pages/ODD/ProtocolDetails/index.tsx index 54731a89c17..931baae070a 100644 --- a/app/src/pages/ODD/ProtocolDetails/index.tsx +++ b/app/src/pages/ODD/ProtocolDetails/index.tsx @@ -37,8 +37,8 @@ import { ProtocolDetailsHeaderChipSkeleton, ProcotolDetailsHeaderTitleSkeleton, ProtocolDetailsSectionContentSkeleton, -} from '../../../organisms/OnDeviceDisplay/ProtocolDetails' -import { useHardwareStatusText } from '../../../organisms/OnDeviceDisplay/RobotDashboard/hooks' +} from '../../../organisms/ODD/ProtocolDetails' +import { useHardwareStatusText } from '../../../organisms/ODD/RobotDashboard/hooks' import { OddModal, SmallModalChildren } from '../../../molecules/OddModal' import { useToaster } from '../../../organisms/ToasterOven' import { @@ -47,11 +47,9 @@ import { updateConfigValue, } from '../../../redux/config' import { useOffsetCandidatesForAnalysis } from '../../../organisms/ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' -import { - useMissingProtocolHardware, - useRunTimeParameters, -} from '../../../pages/Desktop/Protocols/hooks' -import { ProtocolSetupParameters } from '../../../organisms/ProtocolSetupParameters' +import { useRunTimeParameters } from '../../../pages/Desktop/Protocols/hooks' +import { useMissingProtocolHardware } from '../../../transformations/commands' +import { ProtocolSetupParameters } from '../../../organisms/ODD/ProtocolSetup/ProtocolSetupParameters' import { Parameters } from './Parameters' import { Deck } from './Deck' import { Hardware } from './Hardware' diff --git a/app/src/pages/ODD/ProtocolSetup/__tests__/ProtocolSetup.test.tsx b/app/src/pages/ODD/ProtocolSetup/__tests__/ProtocolSetup.test.tsx index 46f648870fe..2622c88e726 100644 --- a/app/src/pages/ODD/ProtocolSetup/__tests__/ProtocolSetup.test.tsx +++ b/app/src/pages/ODD/ProtocolSetup/__tests__/ProtocolSetup.test.tsx @@ -36,31 +36,35 @@ import { } from '../../../../organisms/Devices/hooks' import { getLocalRobot } from '../../../../redux/discovery' import { ANALYTICS_PROTOCOL_RUN_ACTION } from '../../../../redux/analytics' -import { ProtocolSetupLiquids } from '../../../../organisms/ProtocolSetupLiquids' import { getProtocolModulesInfo } from '../../../../organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo' -import { ProtocolSetupModulesAndDeck } from '../../../../organisms/ProtocolSetupModulesAndDeck' -import { ProtocolSetupLabware } from '../../../../organisms/ProtocolSetupLabware' -import { ProtocolSetupOffsets } from '../../../../organisms/ProtocolSetupOffsets' -import { getUnmatchedModulesForProtocol } from '../../../../organisms/ProtocolSetupModulesAndDeck/utils' +import { + ProtocolSetupLabware, + ProtocolSetupLiquids, + ProtocolSetupModulesAndDeck, + ProtocolSetupOffsets, + ViewOnlyParameters, + ProtocolSetupTitleSkeleton, + ProtocolSetupStepSkeleton, + getUnmatchedModulesForProtocol, + getIncompleteInstrumentCount, +} from '../../../../organisms/ODD/ProtocolSetup' import { useLaunchLPC } from '../../../../organisms/LabwarePositionCheck/useLaunchLPC' -import { ConfirmCancelRunModal } from '../../../../organisms/OnDeviceDisplay/RunningProtocol' -import { mockProtocolModuleInfo } from '../../../../organisms/ProtocolSetupInstruments/__fixtures__' -import { getIncompleteInstrumentCount } from '../../../../organisms/ProtocolSetupInstruments/utils' +import { ConfirmCancelRunModal } from '../../../../organisms/ODD/RunningProtocol' +import { mockProtocolModuleInfo } from '../../../../organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/__fixtures__' import { useProtocolHasRunTimeParameters, useRunControls, useRunStatus, } from '../../../../organisms/RunTimeControl/hooks' import { useIsHeaterShakerInProtocol } from '../../../../organisms/ModuleCard/hooks' +import { useNotifyDeckConfigurationQuery } from '../../../../resources/deck_configuration/useNotifyDeckConfigurationQuery' import { useDeckConfigurationCompatibility } from '../../../../resources/deck_configuration/hooks' import { ConfirmAttachedModal } from '../ConfirmAttachedModal' import { ConfirmSetupStepsCompleteModal } from '../ConfirmSetupStepsCompleteModal' import { ProtocolSetup } from '../' import { useNotifyRunQuery } from '../../../../resources/runs' -import { ViewOnlyParameters } from '../../../../organisms/ProtocolSetupParameters/ViewOnlyParameters' import { mockConnectableRobot } from '../../../../redux/discovery/__fixtures__' import { mockRunTimeParameterData } from '../../../../pages/ODD/ProtocolDetails/fixtures' -import { useNotifyDeckConfigurationQuery } from '../../../../resources/deck_configuration' import type { UseQueryResult } from 'react-query' import type * as SharedData from '@opentrons/shared-data' @@ -99,28 +103,32 @@ vi.mock('react-router-dom', async importOriginal => { vi.mock('@opentrons/react-api-client') vi.mock('../../../../organisms/LabwarePositionCheck/useLaunchLPC') vi.mock('../../../../organisms/Devices/hooks') -vi.mock('../../../../organisms/ProtocolSetupParameters/ViewOnlyParameters') +vi.mock('../../../../organisms/ODD/ProtocolSetup', async importOriginal => { + const ACTUALS = ['ProtocolSetupStep'] + const actual = await importOriginal() + return Object.fromEntries( + Object.entries(actual).map(([k, v]) => + ACTUALS.includes(k) ? [k, v] : [k, vi.fn()] + ) + ) +}) vi.mock( '../../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' ) -vi.mock('../../../../organisms/ProtocolSetupInstruments/utils') vi.mock( '../../../../organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo' ) -vi.mock('../../../../organisms/ProtocolSetupModulesAndDeck') -vi.mock('../../../../organisms/ProtocolSetupModulesAndDeck/utils') -vi.mock('../../../../organisms/OnDeviceDisplay/RunningProtocol') +vi.mock('../../../../organisms/ODD/RunningProtocol') vi.mock('../../../../organisms/RunTimeControl/hooks') -vi.mock('../../../../organisms/ProtocolSetupLiquids') -vi.mock('../../../../organisms/ProtocolSetupLabware') -vi.mock('../../../../organisms/ProtocolSetupOffsets') vi.mock('../../../../organisms/ModuleCard/hooks') vi.mock('../../../../redux/discovery/selectors') vi.mock('../ConfirmAttachedModal') vi.mock('../../../../organisms/ToasterOven') -vi.mock('../../../../resources/deck_configuration/hooks') vi.mock('../../../../resources/runs') -vi.mock('../../../../resources/deck_configuration') +vi.mock('../../../../resources/deck_configuration/hooks') +vi.mock( + '../../../../resources/deck_configuration/useNotifyDeckConfigurationQuery' +) vi.mock('../ConfirmSetupStepsCompleteModal') const render = (path = '/') => { @@ -139,6 +147,8 @@ const render = (path = '/') => { const MockProtocolSetupLabware = vi.mocked(ProtocolSetupLabware) const MockProtocolSetupLiquids = vi.mocked(ProtocolSetupLiquids) const MockProtocolSetupOffsets = vi.mocked(ProtocolSetupOffsets) +const MockProtocolSetupTitleSkeleton = vi.mocked(ProtocolSetupTitleSkeleton) +const MockProtocolSetupStepSkeleton = vi.mocked(ProtocolSetupStepSkeleton) const MockConfirmSetupStepsCompleteModal = vi.mocked( ConfirmSetupStepsCompleteModal ) @@ -539,8 +549,10 @@ describe('ProtocolSetup', () => { vi.mocked(useProtocolAnalysisAsDocumentQuery).mockReturnValue({ data: null, } as any) + MockProtocolSetupTitleSkeleton.mockReturnValue(
SKELETON
) + MockProtocolSetupStepSkeleton.mockReturnValue(
SKELETON
) render(`/runs/${RUN_ID}/setup/`) - expect(screen.getAllByTestId('Skeleton').length).toBeGreaterThan(0) + expect(screen.getAllByText('SKELETON').length).toBeGreaterThanOrEqual(2) }) it('should render toast and make a button disabled when a robot door is open', () => { diff --git a/app/src/pages/ODD/ProtocolSetup/index.tsx b/app/src/pages/ODD/ProtocolSetup/index.tsx index ee661c859c4..c0c0e0fe83d 100644 --- a/app/src/pages/ODD/ProtocolSetup/index.tsx +++ b/app/src/pages/ODD/ProtocolSetup/index.tsx @@ -4,25 +4,19 @@ import { useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import { useNavigate, useParams } from 'react-router-dom' import first from 'lodash/first' -import { css } from 'styled-components' import { RUN_STATUS_IDLE, RUN_STATUS_STOPPED } from '@opentrons/api-client' import { ALIGN_CENTER, BORDERS, - Btn, COLORS, DIRECTION_COLUMN, Flex, - Icon, - JUSTIFY_END, JUSTIFY_SPACE_BETWEEN, LegacyStyledText, - NO_WRAP, OVERFLOW_WRAP_ANYWHERE, POSITION_STICKY, SPACING, - TEXT_ALIGN_RIGHT, truncateString, TYPOGRAPHY, useConditionalConfirm, @@ -39,10 +33,6 @@ import { getFixtureDisplayName, } from '@opentrons/shared-data' -import { - ProtocolSetupTitleSkeleton, - ProtocolSetupStepSkeleton, -} from '../../../organisms/OnDeviceDisplay/ProtocolSetup' import { useAttachedModules, useLPCDisabledReason, @@ -52,32 +42,31 @@ import { useRobotType, useTrackProtocolRunEvent, } from '../../../organisms/Devices/hooks' -import { - useRequiredProtocolHardwareFromAnalysis, - useMissingProtocolHardwareFromAnalysis, -} from '../../../pages/Desktop/Protocols/hooks' + import { getProtocolModulesInfo } from '../../../organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo' -import { ProtocolSetupLabware } from '../../../organisms/ProtocolSetupLabware' -import { ProtocolSetupModulesAndDeck } from '../../../organisms/ProtocolSetupModulesAndDeck' -import { ProtocolSetupLiquids } from '../../../organisms/ProtocolSetupLiquids' -import { ProtocolSetupOffsets } from '../../../organisms/ProtocolSetupOffsets' -import { ProtocolSetupInstruments } from '../../../organisms/ProtocolSetupInstruments' -import { ProtocolSetupDeckConfiguration } from '../../../organisms/ProtocolSetupDeckConfiguration' -import { useLaunchLPC } from '../../../organisms/LabwarePositionCheck/useLaunchLPC' -import { getUnmatchedModulesForProtocol } from '../../../organisms/ProtocolSetupModulesAndDeck/utils' -import { ConfirmCancelRunModal } from '../../../organisms/OnDeviceDisplay/RunningProtocol' -import { AnalysisFailedModal } from '../../../organisms/ProtocolSetupParameters/AnalysisFailedModal' import { + AnalysisFailedModal, + ProtocolSetupDeckConfiguration, + ProtocolSetupInstruments, + ProtocolSetupLabware, + ProtocolSetupLiquids, + ProtocolSetupModulesAndDeck, + ProtocolSetupOffsets, + ProtocolSetupStep, + ProtocolSetupStepSkeleton, + ProtocolSetupTitleSkeleton, + getUnmatchedModulesForProtocol, getIncompleteInstrumentCount, - getProtocolUsesGripper, -} from '../../../organisms/ProtocolSetupInstruments/utils' + ViewOnlyParameters, +} from '../../../organisms/ODD/ProtocolSetup' +import { useLaunchLPC } from '../../../organisms/LabwarePositionCheck/useLaunchLPC' +import { ConfirmCancelRunModal } from '../../../organisms/ODD/RunningProtocol' import { useRunControls, useRunStatus, } from '../../../organisms/RunTimeControl/hooks' import { useToaster } from '../../../organisms/ToasterOven' import { useIsHeaterShakerInProtocol } from '../../../organisms/ModuleCard/hooks' -import { getLabwareSetupItemGroups } from '../../../pages/Desktop/Protocols/utils' import { getLocalRobot, getRobotSerialNumber } from '../../../redux/discovery' import { ANALYTICS_PROTOCOL_PROCEED_TO_RUN, @@ -92,189 +81,25 @@ import { CloseButton, PlayButton } from './Buttons' import { useDeckConfigurationCompatibility } from '../../../resources/deck_configuration/hooks' import { getRequiredDeckConfig } from '../../../resources/deck_configuration/utils' import { useNotifyRunQuery } from '../../../resources/runs' -import { ViewOnlyParameters } from '../../../organisms/ProtocolSetupParameters/ViewOnlyParameters' import type { Run } from '@opentrons/api-client' import type { CutoutFixtureId, CutoutId } from '@opentrons/shared-data' import type { OnDeviceRouteParams } from '../../../App/types' +import type { ProtocolModuleInfo } from '../../../organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo' +import type { SetupScreens } from '../../../organisms/ODD/ProtocolSetup' import type { ProtocolHardware, ProtocolFixture, -} from '../../../pages/Desktop/Protocols/hooks' -import type { ProtocolModuleInfo } from '../../../organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo' +} from '../../../transformations/commands' +import { + getLabwareSetupItemGroups, + getProtocolUsesGripper, + useRequiredProtocolHardwareFromAnalysis, + useMissingProtocolHardwareFromAnalysis, +} from '../../../transformations/commands' const FETCH_DURATION_MS = 5000 -export type ProtocolSetupStepStatus = - | 'ready' - | 'not ready' - | 'general' - | 'inform' -interface ProtocolSetupStepProps { - onClickSetupStep: () => void - status: ProtocolSetupStepStatus - title: string - // first line of detail text - detail?: string | null - // clip detail text overflow with ellipsis - clipDetail?: boolean - // second line of detail text - subDetail?: string | null - // disallow click handler, disabled styling - disabled?: boolean - // disallow click handler, don't show CTA icons, allow styling - interactionDisabled?: boolean - // display the reason the setup step is disabled - disabledReason?: string | null - // optional description - description?: string | null - // optional removal of the left icon - hasLeftIcon?: boolean - // optional removal of the right icon - hasRightIcon?: boolean - // optional enlarge the font size - fontSize?: string -} - -export function ProtocolSetupStep({ - onClickSetupStep, - status, - title, - detail, - subDetail, - disabled = false, - clipDetail = false, - interactionDisabled = false, - disabledReason, - description, - hasRightIcon = true, - hasLeftIcon = true, - fontSize = 'p', -}: ProtocolSetupStepProps): JSX.Element { - const isInteractionDisabled = interactionDisabled || disabled - const backgroundColorByStepStatus = { - ready: COLORS.green35, - 'not ready': COLORS.yellow35, - general: COLORS.grey35, - inform: COLORS.grey35, - } - const { makeSnackbar } = useToaster() - - const makeDisabledReasonSnackbar = (): void => { - if (disabledReason != null) { - makeSnackbar(disabledReason) - } - } - - let backgroundColor: string - if (!disabled) { - switch (status) { - case 'general': - backgroundColor = COLORS.blue35 - break - case 'ready': - backgroundColor = COLORS.green40 - break - case 'inform': - backgroundColor = COLORS.grey50 - break - default: - backgroundColor = COLORS.yellow40 - } - } else backgroundColor = '' - - const PUSHED_STATE_STYLE = css` - &:active { - background-color: ${backgroundColor}; - } - ` - - const isToggle = detail === 'On' || detail === 'Off' - - return ( - { - !isInteractionDisabled - ? onClickSetupStep() - : makeDisabledReasonSnackbar() - }} - width="100%" - data-testid={`SetupButton_${title}`} - > - - {status !== 'general' && - !disabled && - status !== 'inform' && - hasLeftIcon ? ( - - ) : null} - - - {title} - - {description != null ? ( - - {description} - - ) : null} - - - - {detail} - {subDetail != null && detail != null ?
: null} - {subDetail} -
-
- {interactionDisabled || !hasRightIcon ? null : ( - - )} -
-
- ) -} - const ANALYSIS_POLL_MS = 5000 interface PrepareToRunProps { runId: string @@ -835,16 +660,6 @@ function PrepareToRun({ ) } -export type SetupScreens = - | 'prepare to run' - | 'instruments' - | 'modules' - | 'offsets' - | 'labware' - | 'liquids' - | 'deck configuration' - | 'view only parameters' - export function ProtocolSetup(): JSX.Element { const { runId } = useParams< keyof OnDeviceRouteParams @@ -1080,9 +895,3 @@ export function ProtocolSetup(): JSX.Element { ) } - -const CLIPPED_TEXT_STYLE = css` - white-space: ${NO_WRAP}; - overflow: hidden; - text-overflow: ellipsis; -` diff --git a/app/src/pages/ODD/QuickTransferDetails/Hardware.tsx b/app/src/pages/ODD/QuickTransferDetails/Hardware.tsx index 777bf4c0b11..f8765ead16d 100644 --- a/app/src/pages/ODD/QuickTransferDetails/Hardware.tsx +++ b/app/src/pages/ODD/QuickTransferDetails/Hardware.tsx @@ -32,7 +32,7 @@ import { useRequiredProtocolHardware } from '../../../pages/Desktop/Protocols/ho import type { ProtocolHardware, ProtocolPipette, -} from '../../../pages/Desktop/Protocols/hooks' +} from '../../../transformations/commands' import type { TFunction } from 'i18next' const Table = styled('table')` diff --git a/app/src/pages/ODD/QuickTransferDetails/__tests__/Hardware.test.tsx b/app/src/pages/ODD/QuickTransferDetails/__tests__/Hardware.test.tsx index 515528b3ea9..460f00178e7 100644 --- a/app/src/pages/ODD/QuickTransferDetails/__tests__/Hardware.test.tsx +++ b/app/src/pages/ODD/QuickTransferDetails/__tests__/Hardware.test.tsx @@ -12,6 +12,7 @@ import { i18n } from '../../../../i18n' import { useRequiredProtocolHardware } from '../../../../pages/Desktop/Protocols/hooks' import { Hardware } from '../Hardware' +vi.mock('../../../../transformations/commands') vi.mock('../../../../pages/Desktop/Protocols/hooks') vi.mock('../../../../redux/config') diff --git a/app/src/pages/ODD/QuickTransferDetails/__tests__/QuickTransferDetails.test.tsx b/app/src/pages/ODD/QuickTransferDetails/__tests__/QuickTransferDetails.test.tsx index c32efca15e7..9a5f16dbd02 100644 --- a/app/src/pages/ODD/QuickTransferDetails/__tests__/QuickTransferDetails.test.tsx +++ b/app/src/pages/ODD/QuickTransferDetails/__tests__/QuickTransferDetails.test.tsx @@ -12,9 +12,9 @@ import { useProtocolAnalysisAsDocumentQuery, } from '@opentrons/react-api-client' import { i18n } from '../../../../i18n' -import { useHardwareStatusText } from '../../../../organisms/OnDeviceDisplay/RobotDashboard/hooks' +import { useHardwareStatusText } from '../../../../organisms/ODD/RobotDashboard/hooks' import { useOffsetCandidatesForAnalysis } from '../../../../organisms/ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' -import { useMissingProtocolHardware } from '../../../../pages/Desktop/Protocols/hooks' +import { useMissingProtocolHardware } from '../../../../transformations/commands' import { formatTimeWithUtcLabel } from '../../../../resources/runs' import { DeleteTransferConfirmationModal } from '../../QuickTransferDashboard/DeleteTransferConfirmationModal' import { QuickTransferDetails } from '..' @@ -36,15 +36,15 @@ Object.defineProperty(window, 'IntersectionObserver', { configurable: true, value: IntersectionObserver, }) -vi.mock('../../../../organisms/ProtocolSetupParameters') +vi.mock('../../../../organisms/ODD/ProtocolSetup/ProtocolSetupParameters') vi.mock('@opentrons/api-client') vi.mock('@opentrons/react-api-client') -vi.mock('../../../../organisms/OnDeviceDisplay/RobotDashboard/hooks') +vi.mock('../../../../organisms/ODD/RobotDashboard/hooks') vi.mock( '../../../../organisms/ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' ) vi.mock('../../QuickTransferDashboard/DeleteTransferConfirmationModal') -vi.mock('../../../../pages/Desktop/Protocols/hooks') +vi.mock('../../../../transformations/commands') vi.mock('../Deck') vi.mock('../Hardware') vi.mock('../Labware') diff --git a/app/src/pages/ODD/QuickTransferDetails/index.tsx b/app/src/pages/ODD/QuickTransferDetails/index.tsx index 24877185fb7..e0b92bd1f6c 100644 --- a/app/src/pages/ODD/QuickTransferDetails/index.tsx +++ b/app/src/pages/ODD/QuickTransferDetails/index.tsx @@ -36,8 +36,8 @@ import { ProtocolDetailsHeaderChipSkeleton, ProcotolDetailsHeaderTitleSkeleton, ProtocolDetailsSectionContentSkeleton, -} from '../../../organisms/OnDeviceDisplay/ProtocolDetails' -import { useHardwareStatusText } from '../../../organisms/OnDeviceDisplay/RobotDashboard/hooks' +} from '../../../organisms/ODD/ProtocolDetails' +import { useHardwareStatusText } from '../../../organisms/ODD/RobotDashboard/hooks' import { SmallModalChildren } from '../../../molecules/OddModal' import { useToaster } from '../../../organisms/ToasterOven' import { @@ -46,7 +46,7 @@ import { updateConfigValue, } from '../../../redux/config' import { useOffsetCandidatesForAnalysis } from '../../../organisms/ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' -import { useMissingProtocolHardware } from '../../../pages/Desktop/Protocols/hooks' +import { useMissingProtocolHardware } from '../../../transformations/commands' import { DeleteTransferConfirmationModal } from '../QuickTransferDashboard/DeleteTransferConfirmationModal' import { Deck } from './Deck' import { Hardware } from './Hardware' diff --git a/app/src/pages/ODD/RobotDashboard/__tests__/RobotDashboard.test.tsx b/app/src/pages/ODD/RobotDashboard/__tests__/RobotDashboard.test.tsx index 809ca74a478..55a090c4fb8 100644 --- a/app/src/pages/ODD/RobotDashboard/__tests__/RobotDashboard.test.tsx +++ b/app/src/pages/ODD/RobotDashboard/__tests__/RobotDashboard.test.tsx @@ -10,9 +10,9 @@ import { i18n } from '../../../../i18n' import { RecentRunProtocolCarousel, EmptyRecentRun, -} from '../../../../organisms/OnDeviceDisplay/RobotDashboard' +} from '../../../../organisms/ODD/RobotDashboard' import { Navigation } from '../../../../organisms/Navigation' -import { useMissingProtocolHardware } from '../../../../pages/Desktop/Protocols/hooks' +import { useMissingProtocolHardware } from '../../../../transformations/commands' import { getOnDeviceDisplaySettings } from '../../../../redux/config' import { WelcomeModal } from '../WelcomeModal' import { RobotDashboard } from '..' @@ -31,12 +31,10 @@ vi.mock('react-router-dom', async importOriginal => { } }) vi.mock('@opentrons/react-api-client') -vi.mock('../../../../organisms/OnDeviceDisplay/RobotDashboard/EmptyRecentRun') -vi.mock( - '../../../../organisms/OnDeviceDisplay/RobotDashboard/RecentRunProtocolCarousel' -) +vi.mock('../../../../organisms/ODD/RobotDashboard/EmptyRecentRun') +vi.mock('../../../../organisms/ODD/RobotDashboard/RecentRunProtocolCarousel') vi.mock('../../../../organisms/Navigation') -vi.mock('../../../../pages/Desktop/Protocols/hooks') +vi.mock('../../../../transformations/commands') vi.mock('../../../../redux/config') vi.mock('../WelcomeModal') vi.mock('../../../../resources/runs') diff --git a/app/src/pages/ODD/RobotDashboard/index.tsx b/app/src/pages/ODD/RobotDashboard/index.tsx index 764c6dccbfd..609e57bca65 100644 --- a/app/src/pages/ODD/RobotDashboard/index.tsx +++ b/app/src/pages/ODD/RobotDashboard/index.tsx @@ -16,10 +16,10 @@ import { Navigation } from '../../../organisms/Navigation' import { EmptyRecentRun, RecentRunProtocolCarousel, -} from '../../../organisms/OnDeviceDisplay/RobotDashboard' +} from '../../../organisms/ODD/RobotDashboard' import { getOnDeviceDisplaySettings } from '../../../redux/config' import { WelcomeModal } from './WelcomeModal' -import { ServerInitializing } from '../../../organisms/OnDeviceDisplay/RobotDashboard/ServerInitializing' +import { ServerInitializing } from '../../../organisms/ODD/RobotDashboard/ServerInitializing' import { useNotifyAllRunsQuery } from '../../../resources/runs' import type { RunData } from '@opentrons/api-client' diff --git a/app/src/pages/ODD/RunSummary/index.tsx b/app/src/pages/ODD/RunSummary/index.tsx index dea605476c2..800d14ec9c1 100644 --- a/app/src/pages/ODD/RunSummary/index.tsx +++ b/app/src/pages/ODD/RunSummary/index.tsx @@ -60,7 +60,7 @@ import { ANALYTICS_PROTOCOL_PROCEED_TO_RUN, } from '../../../redux/analytics' import { getLocalRobot } from '../../../redux/discovery' -import { RunFailedModal } from '../../../organisms/OnDeviceDisplay/RunningProtocol' +import { RunFailedModal } from '../../../organisms/ODD/RunningProtocol' import { formatTimeWithUtcLabel, useIsRunCurrent, diff --git a/app/src/pages/ODD/RunningProtocol/__tests__/RunningProtocol.test.tsx b/app/src/pages/ODD/RunningProtocol/__tests__/RunningProtocol.test.tsx index 866f33e34e7..2757da982f2 100644 --- a/app/src/pages/ODD/RunningProtocol/__tests__/RunningProtocol.test.tsx +++ b/app/src/pages/ODD/RunningProtocol/__tests__/RunningProtocol.test.tsx @@ -22,14 +22,14 @@ import { mockRobotSideAnalysis } from '../../../../molecules/Command/__fixtures_ import { CurrentRunningProtocolCommand, RunningProtocolSkeleton, -} from '../../../../organisms/OnDeviceDisplay/RunningProtocol' +} from '../../../../organisms/ODD/RunningProtocol' import { mockUseAllCommandsResponseNonDeterministic } from '../../../../organisms/RunProgressMeter/__fixtures__' import { useRunStatus, useRunTimestamps, } from '../../../../organisms/RunTimeControl/hooks' import { getLocalRobot } from '../../../../redux/discovery' -import { CancelingRunModal } from '../../../../organisms/OnDeviceDisplay/RunningProtocol/CancelingRunModal' +import { CancelingRunModal } from '../../../../organisms/ODD/RunningProtocol/CancelingRunModal' import { useTrackProtocolRunEvent } from '../../../../organisms/Devices/hooks' import { useMostRecentCompletedAnalysis } from '../../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' import { OpenDoorAlertModal } from '../../../../organisms/OpenDoorAlertModal' @@ -60,11 +60,9 @@ vi.mock( '../../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' ) vi.mock('../../../../organisms/RunTimeControl/hooks') -vi.mock('../../../../organisms/OnDeviceDisplay/RunningProtocol') +vi.mock('../../../../organisms/ODD/RunningProtocol') vi.mock('../../../../redux/discovery') -vi.mock( - '../../../../organisms/OnDeviceDisplay/RunningProtocol/CancelingRunModal' -) +vi.mock('../../../../organisms/ODD/RunningProtocol/CancelingRunModal') vi.mock('../../../../organisms/OpenDoorAlertModal') vi.mock('../../../../resources/runs') vi.mock('../../../../redux/config') diff --git a/app/src/pages/ODD/RunningProtocol/index.tsx b/app/src/pages/ODD/RunningProtocol/index.tsx index d5783835b16..02d961a0b3a 100644 --- a/app/src/pages/ODD/RunningProtocol/index.tsx +++ b/app/src/pages/ODD/RunningProtocol/index.tsx @@ -41,14 +41,14 @@ import { CurrentRunningProtocolCommand, RunningProtocolCommandList, RunningProtocolSkeleton, -} from '../../../organisms/OnDeviceDisplay/RunningProtocol' +} from '../../../organisms/ODD/RunningProtocol' import { useTrackProtocolRunEvent, useRobotAnalyticsData, useRobotType, } from '../../../organisms/Devices/hooks' -import { CancelingRunModal } from '../../../organisms/OnDeviceDisplay/RunningProtocol/CancelingRunModal' -import { ConfirmCancelRunModal } from '../../../organisms/OnDeviceDisplay/RunningProtocol/ConfirmCancelRunModal' +import { CancelingRunModal } from '../../../organisms/ODD/RunningProtocol/CancelingRunModal' +import { ConfirmCancelRunModal } from '../../../organisms/ODD/RunningProtocol/ConfirmCancelRunModal' import { getLocalRobot } from '../../../redux/discovery' import { OpenDoorAlertModal } from '../../../organisms/OpenDoorAlertModal' import { diff --git a/app/src/transformations/commands/hooks/__tests__/useMissingProtocolHardware.test.tsx b/app/src/transformations/commands/hooks/__tests__/useMissingProtocolHardware.test.tsx new file mode 100644 index 00000000000..303ea03a784 --- /dev/null +++ b/app/src/transformations/commands/hooks/__tests__/useMissingProtocolHardware.test.tsx @@ -0,0 +1,347 @@ +import omitBy from 'lodash/omitBy' +import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' +import type { UseQueryResult } from 'react-query' +import { renderHook } from '@testing-library/react' +import type { Protocol } from '@opentrons/api-client' +import { + useProtocolQuery, + useProtocolAnalysisAsDocumentQuery, + useInstrumentsQuery, + useModulesQuery, +} from '@opentrons/react-api-client' +import { + FLEX_SIMPLEST_DECK_CONFIG, + WASTE_CHUTE_RIGHT_ADAPTER_NO_COVER_FIXTURE, + fixtureTiprack300ul, +} from '@opentrons/shared-data' +import type { + CompletedProtocolAnalysis, + DeckConfiguration, + LabwareDefinition2, +} from '@opentrons/shared-data' +import { useNotifyDeckConfigurationQuery } from '../../../../resources/deck_configuration/useNotifyDeckConfigurationQuery' +import { useMissingProtocolHardware } from '../useMissingProtocolHardware' +import { mockHeaterShaker } from '../../../../redux/modules/__fixtures__' + +vi.mock('@opentrons/react-api-client') +vi.mock( + '../../../../resources/deck_configuration/useNotifyDeckConfigurationQuery' +) + +const mockRTPData = [ + { + displayName: 'Dry Run', + variableName: 'DRYRUN', + description: 'a dry run description', + type: 'bool', + default: false, + }, + { + displayName: 'Use Gripper', + variableName: 'USE_GRIPPER', + description: '', + type: 'bool', + default: true, + }, + { + displayName: 'Trash Tips', + variableName: 'TIP_TRASH', + description: 'throw tip in trash', + type: 'bool', + default: true, + }, + { + displayName: 'Deactivate Temperatures', + variableName: 'DEACTIVATE_TEMP', + description: 'deactivate temperature?', + type: 'bool', + default: true, + }, + { + displayName: 'Columns of Samples', + variableName: 'COLUMNS', + description: '', + suffix: 'mL', + type: 'int', + min: 1, + max: 14, + default: 4, + }, + { + displayName: 'PCR Cycles', + variableName: 'PCR_CYCLES', + description: '', + type: 'int', + min: 1, + max: 10, + default: 6, + }, + { + displayName: 'EtoH Volume', + variableName: 'ETOH_VOLUME', + description: '', + type: 'float', + min: 1.5, + max: 10.0, + default: 6.5, + }, + { + displayName: 'Default Module Offsets', + variableName: 'DEFAULT_OFFSETS', + description: '', + type: 'str', + choices: [ + { + displayName: 'no offsets', + value: 'none', + }, + { + displayName: 'temp offset', + value: '1', + }, + { + displayName: 'heater-shaker offset', + value: '2', + }, + ], + default: 'none', + }, +] +const mockLabwareDef = fixtureTiprack300ul as LabwareDefinition2 +const PROTOCOL_ANALYSIS = { + id: 'fake analysis', + status: 'completed', + labware: [], + pipettes: [{ id: 'pipId', pipetteName: 'p1000_multi_flex', mount: 'left' }], + modules: [ + { + id: 'modId', + model: 'heaterShakerModuleV1', + location: { slotName: 'D3' }, + serialNumber: 'serialNum', + }, + ], + commands: [ + { + key: 'CommandKey0', + commandType: 'loadModule', + params: { + model: 'heaterShakerModuleV1', + location: { slotName: 'D3' }, + }, + result: { + moduleId: 'modId', + }, + id: 'CommandId0', + status: 'succeeded', + error: null, + createdAt: 'fakeCreatedAtTimestamp', + startedAt: 'fakeStartedAtTimestamp', + completedAt: 'fakeCompletedAtTimestamp', + }, + { + key: 'CommandKey1', + commandType: 'loadLabware', + params: { + labwareId: 'firstLabwareId', + location: { moduleId: 'modId' }, + displayName: 'first labware nickname', + }, + result: { + labwareId: 'firstLabwareId', + definition: mockLabwareDef, + offset: { x: 0, y: 0, z: 0 }, + }, + id: 'CommandId1', + status: 'succeeded', + error: null, + createdAt: 'fakeCreatedAtTimestamp', + startedAt: 'fakeStartedAtTimestamp', + completedAt: 'fakeCompletedAtTimestamp', + }, + ], + runTimeParameters: mockRTPData, +} as any +describe.only('useMissingProtocolHardware', () => { + let wrapper: React.FunctionComponent<{ children: React.ReactNode }> + beforeEach(() => { + vi.mocked(useInstrumentsQuery).mockReturnValue({ + data: { data: [] }, + isLoading: false, + } as any) + vi.mocked(useModulesQuery).mockReturnValue({ + data: { data: [] }, + isLoading: false, + } as any) + vi.mocked(useProtocolQuery).mockReturnValue({ + data: { + data: { analysisSummaries: [{ id: PROTOCOL_ANALYSIS.id } as any] }, + }, + } as UseQueryResult) + vi.mocked(useProtocolAnalysisAsDocumentQuery).mockReturnValue({ + data: PROTOCOL_ANALYSIS, + } as UseQueryResult) + vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ + data: [{}], + } as UseQueryResult) + }) + + afterEach(() => { + vi.resetAllMocks() + }) + it('should return 1 pipette and 1 module', () => { + const { result } = renderHook( + () => useMissingProtocolHardware(PROTOCOL_ANALYSIS.id), + { wrapper } + ) + expect(result.current).toEqual({ + isLoading: false, + missingProtocolHardware: [ + { + hardwareType: 'pipette', + pipetteName: 'p1000_multi_flex', + mount: 'left', + connected: false, + }, + { + hardwareType: 'module', + moduleModel: 'heaterShakerModuleV1', + slot: 'D3', + connected: false, + hasSlotConflict: false, + }, + ], + conflictedSlots: [], + }) + }) + it('should return 1 conflicted slot', () => { + vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue(({ + data: [ + { + cutoutId: 'cutoutD3', + cutoutFixtureId: WASTE_CHUTE_RIGHT_ADAPTER_NO_COVER_FIXTURE, + }, + ], + } as any) as UseQueryResult) + + const { result } = renderHook( + () => useMissingProtocolHardware(PROTOCOL_ANALYSIS.id), + { wrapper } + ) + expect(result.current).toEqual({ + isLoading: false, + missingProtocolHardware: [ + { + hardwareType: 'pipette', + pipetteName: 'p1000_multi_flex', + mount: 'left', + connected: false, + }, + { + hardwareType: 'module', + moduleModel: 'heaterShakerModuleV1', + slot: 'D3', + connected: false, + hasSlotConflict: true, + }, + ], + conflictedSlots: ['D3'], + }) + }) + it('should return empty array when the correct modules and pipettes are attached', () => { + vi.mocked(useInstrumentsQuery).mockReturnValue({ + data: { + data: [ + { + mount: 'left', + instrumentType: 'pipette', + instrumentName: 'p1000_multi_flex', + ok: true, + }, + ], + }, + isLoading: false, + } as any) + + vi.mocked(useModulesQuery).mockReturnValue({ + data: { data: [mockHeaterShaker] }, + isLoading: false, + } as any) + vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ + data: [ + omitBy( + FLEX_SIMPLEST_DECK_CONFIG, + ({ cutoutId }) => cutoutId === 'cutoutD3' + ), + { + cutoutId: 'cutoutD3', + cutoutFixtureId: 'heaterShakerModuleV1', + opentronsModuleSerialNumber: mockHeaterShaker.serialNumber, + }, + ], + isLoading: false, + } as any) + + const { result } = renderHook( + () => useMissingProtocolHardware(PROTOCOL_ANALYSIS.id), + { wrapper } + ) + expect(result.current).toEqual({ + missingProtocolHardware: [], + isLoading: false, + conflictedSlots: [], + }) + }) + it('should return conflicting slot when module location is configured with something other than module fixture', () => { + vi.mocked(useInstrumentsQuery).mockReturnValue({ + data: { + data: [ + { + mount: 'left', + instrumentType: 'pipette', + instrumentName: 'p1000_multi_flex', + ok: true, + }, + ], + }, + isLoading: false, + } as any) + + vi.mocked(useModulesQuery).mockReturnValue({ + data: { data: [mockHeaterShaker] }, + isLoading: false, + } as any) + + vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ + data: [ + omitBy( + FLEX_SIMPLEST_DECK_CONFIG, + ({ cutoutId }) => cutoutId === 'cutoutD3' + ), + { + cutoutId: 'cutoutD3', + cutoutFixtureId: WASTE_CHUTE_RIGHT_ADAPTER_NO_COVER_FIXTURE, + }, + ], + isLoading: false, + } as any) + + const { result } = renderHook( + () => useMissingProtocolHardware(PROTOCOL_ANALYSIS.id), + { wrapper } + ) + expect(result.current).toEqual({ + missingProtocolHardware: [ + { + hardwareType: 'module', + moduleModel: 'heaterShakerModuleV1', + slot: 'D3', + connected: false, + hasSlotConflict: true, + }, + ], + isLoading: false, + conflictedSlots: ['D3'], + }) + }) +}) diff --git a/app/src/transformations/commands/hooks/index.ts b/app/src/transformations/commands/hooks/index.ts new file mode 100644 index 00000000000..4765bb03363 --- /dev/null +++ b/app/src/transformations/commands/hooks/index.ts @@ -0,0 +1,5 @@ +export * from './useMissingProtocolHardware' +export * from './useMissingProtocolHardwareFromRequiredProtocolHardware' +export * from './useRequiredProtocolHardwareFromAnalysis' +export * from './useMissingProtocolHardwareFromAnalysis' +export * from './types' diff --git a/app/src/transformations/commands/hooks/types.ts b/app/src/transformations/commands/hooks/types.ts new file mode 100644 index 00000000000..c2a37e639c9 --- /dev/null +++ b/app/src/transformations/commands/hooks/types.ts @@ -0,0 +1,39 @@ +import type { + CutoutFixtureId, + CutoutId, + ModuleModel, + PipetteName, +} from '@opentrons/shared-data' + +export interface ProtocolPipette { + hardwareType: 'pipette' + pipetteName: PipetteName + mount: 'left' | 'right' + connected: boolean +} + +export interface ProtocolModule { + hardwareType: 'module' + moduleModel: ModuleModel + slot: string + connected: boolean + hasSlotConflict: boolean +} + +export interface ProtocolGripper { + hardwareType: 'gripper' + connected: boolean +} + +export interface ProtocolFixture { + hardwareType: 'fixture' + cutoutFixtureId: CutoutFixtureId | null + location: { cutout: CutoutId } + hasSlotConflict: boolean +} + +export type ProtocolHardware = + | ProtocolPipette + | ProtocolModule + | ProtocolGripper + | ProtocolFixture diff --git a/app/src/transformations/commands/hooks/useMissingProtocolHardware.ts b/app/src/transformations/commands/hooks/useMissingProtocolHardware.ts new file mode 100644 index 00000000000..423a7d61e74 --- /dev/null +++ b/app/src/transformations/commands/hooks/useMissingProtocolHardware.ts @@ -0,0 +1,36 @@ +import last from 'lodash/last' +import { + useProtocolQuery, + useProtocolAnalysisAsDocumentQuery, +} from '@opentrons/react-api-client' + +import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' +import type { ProtocolHardware } from './types' +import { useRequiredProtocolHardwareFromAnalysis } from './useRequiredProtocolHardwareFromAnalysis' +import { useMissingProtocolHardwareFromRequiredProtocolHardware } from './useMissingProtocolHardwareFromRequiredProtocolHardware' + +export const useMissingProtocolHardware = ( + protocolId: string +): { + missingProtocolHardware: ProtocolHardware[] + conflictedSlots: string[] + isLoading: boolean +} => { + const { data: protocolData } = useProtocolQuery(protocolId) + const { data: analysis } = useProtocolAnalysisAsDocumentQuery( + protocolId, + last(protocolData?.data.analysisSummaries)?.id ?? null, + { enabled: protocolData != null } + ) + const { + requiredProtocolHardware, + isLoading, + } = useRequiredProtocolHardwareFromAnalysis(analysis ?? null) + + return useMissingProtocolHardwareFromRequiredProtocolHardware( + requiredProtocolHardware, + isLoading, + FLEX_ROBOT_TYPE, + analysis ?? null + ) +} diff --git a/app/src/transformations/commands/hooks/useMissingProtocolHardwareFromAnalysis.ts b/app/src/transformations/commands/hooks/useMissingProtocolHardwareFromAnalysis.ts new file mode 100644 index 00000000000..33879c38370 --- /dev/null +++ b/app/src/transformations/commands/hooks/useMissingProtocolHardwareFromAnalysis.ts @@ -0,0 +1,28 @@ +import type { + RobotType, + CompletedProtocolAnalysis, +} from '@opentrons/shared-data' +import { useRequiredProtocolHardwareFromAnalysis } from './useRequiredProtocolHardwareFromAnalysis' +import { useMissingProtocolHardwareFromRequiredProtocolHardware } from './useMissingProtocolHardwareFromRequiredProtocolHardware' +import type { ProtocolHardware } from './types' + +export const useMissingProtocolHardwareFromAnalysis = ( + robotType: RobotType, + analysis: CompletedProtocolAnalysis | null +): { + missingProtocolHardware: ProtocolHardware[] + conflictedSlots: string[] + isLoading: boolean +} => { + const { + requiredProtocolHardware, + isLoading, + } = useRequiredProtocolHardwareFromAnalysis(analysis) + + return useMissingProtocolHardwareFromRequiredProtocolHardware( + requiredProtocolHardware, + isLoading, + robotType, + analysis ?? null + ) +} diff --git a/app/src/transformations/commands/hooks/useMissingProtocolHardwareFromRequiredProtocolHardware.ts b/app/src/transformations/commands/hooks/useMissingProtocolHardwareFromRequiredProtocolHardware.ts new file mode 100644 index 00000000000..5b94be3cc87 --- /dev/null +++ b/app/src/transformations/commands/hooks/useMissingProtocolHardwareFromRequiredProtocolHardware.ts @@ -0,0 +1,74 @@ +import { FLEX_MODULE_ADDRESSABLE_AREAS } from '@opentrons/shared-data' +import type { + CompletedProtocolAnalysis, + ProtocolAnalysisOutput, + RobotType, +} from '@opentrons/shared-data' +import { useDeckConfigurationCompatibility } from '../../../resources/deck_configuration/hooks' +import type { ProtocolHardware, ProtocolModule, ProtocolFixture } from './types' + +/** + * Returns an array of ProtocolHardware objects that are required by the given protocol ID, + * but not currently connected. + * + * @param {ProtocolHardware[]} requiredProtocolHardware An array of ProtocolHardware objects that are required by a protocol. + * @param {boolean} isLoading A boolean determining whether any required protocol hardware is loading. + * @returns {ProtocolHardware[]} An array of ProtocolHardware objects that are required by the given protocol ID, but not currently connected. + */ + +export const useMissingProtocolHardwareFromRequiredProtocolHardware = ( + requiredProtocolHardware: ProtocolHardware[], + isLoading: boolean, + robotType: RobotType, + protocolAnalysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput | null +): { + missingProtocolHardware: ProtocolHardware[] + conflictedSlots: string[] + isLoading: boolean +} => { + const deckConfigCompatibility = useDeckConfigurationCompatibility( + robotType, + protocolAnalysis + ) + // determine missing or conflicted hardware + return { + missingProtocolHardware: [ + ...requiredProtocolHardware.filter( + hardware => 'connected' in hardware && !hardware.connected + ), + ...deckConfigCompatibility + .filter( + ({ + cutoutFixtureId, + compatibleCutoutFixtureIds, + requiredAddressableAreas, + }) => + cutoutFixtureId != null && + !compatibleCutoutFixtureIds.some(id => id === cutoutFixtureId) && + !FLEX_MODULE_ADDRESSABLE_AREAS.some(modAA => + requiredAddressableAreas.includes(modAA) + ) // modules are already included via requiredProtocolHardware + ) + .map(({ compatibleCutoutFixtureIds, cutoutId }) => ({ + hardwareType: 'fixture' as const, + cutoutFixtureId: compatibleCutoutFixtureIds[0], + location: { cutout: cutoutId }, + hasSlotConflict: true, + })), + ], + conflictedSlots: requiredProtocolHardware + .filter( + (hardware): hardware is ProtocolModule | ProtocolFixture => + (hardware.hardwareType === 'module' || + hardware.hardwareType === 'fixture') && + hardware.hasSlotConflict + ) + .map( + hardware => + hardware.hardwareType === 'module' + ? hardware.slot // module + : hardware.location.cutout // fixture + ), + isLoading, + } +} diff --git a/app/src/transformations/commands/hooks/useRequiredProtocolHardwareFromAnalysis.ts b/app/src/transformations/commands/hooks/useRequiredProtocolHardwareFromAnalysis.ts new file mode 100644 index 00000000000..95b5886072c --- /dev/null +++ b/app/src/transformations/commands/hooks/useRequiredProtocolHardwareFromAnalysis.ts @@ -0,0 +1,165 @@ +import { + useInstrumentsQuery, + useModulesQuery, +} from '@opentrons/react-api-client' +import { + FLEX_ROBOT_TYPE, + getCutoutIdForSlotName, + getDeckDefFromRobotType, + getCutoutFixturesForModuleModel, + getCutoutFixtureIdsForModuleModel, + getModuleType, + FLEX_USB_MODULE_ADDRESSABLE_AREAS, + FLEX_SINGLE_SLOT_ADDRESSABLE_AREAS, + MAGNETIC_BLOCK_TYPE, +} from '@opentrons/shared-data' +import type { CompletedProtocolAnalysis } from '@opentrons/shared-data' +import { + useNotifyDeckConfigurationQuery, + useDeckConfigurationCompatibility, +} from '../../../resources/deck_configuration' +import type { + ProtocolHardware, + ProtocolGripper, + ProtocolModule, + ProtocolPipette, +} from './types' +import { getProtocolUsesGripper } from '../transformations' + +const DECK_CONFIG_REFETCH_INTERVAL = 5000 + +export const useRequiredProtocolHardwareFromAnalysis = ( + analysis: CompletedProtocolAnalysis | null +): { requiredProtocolHardware: ProtocolHardware[]; isLoading: boolean } => { + const { + data: attachedModulesData, + isLoading: isLoadingModules, + } = useModulesQuery() + const attachedModules = attachedModulesData?.data ?? [] + + const { + data: attachedInstrumentsData, + isLoading: isLoadingInstruments, + } = useInstrumentsQuery() + const attachedInstruments = attachedInstrumentsData?.data ?? [] + + const robotType = FLEX_ROBOT_TYPE + const deckDef = getDeckDefFromRobotType(robotType) + const deckConfig = + useNotifyDeckConfigurationQuery({ + refetchInterval: DECK_CONFIG_REFETCH_INTERVAL, + })?.data ?? [] + const deckConfigCompatibility = useDeckConfigurationCompatibility( + robotType, + analysis + ) + + if (analysis == null || analysis?.status !== 'completed') { + return { requiredProtocolHardware: [], isLoading: true } + } + + const requiredGripper: ProtocolGripper[] = getProtocolUsesGripper(analysis) + ? [ + { + hardwareType: 'gripper', + connected: + attachedInstruments.some(i => i.instrumentType === 'gripper') ?? + false, + }, + ] + : [] + + const requiredModules: ProtocolModule[] = analysis.modules + // remove magnetic blocks, they're handled by required fixtures + .filter(m => getModuleType(m.model) !== MAGNETIC_BLOCK_TYPE) + .map(({ location, model }) => { + const cutoutIdForSlotName = getCutoutIdForSlotName( + location.slotName, + deckDef + ) + const moduleFixtures = getCutoutFixturesForModuleModel(model, deckDef) + + const configuredModuleSerialNumber = + deckConfig.find( + ({ cutoutId, cutoutFixtureId }) => + cutoutId === cutoutIdForSlotName && + moduleFixtures.map(mf => mf.id).includes(cutoutFixtureId) + )?.opentronsModuleSerialNumber ?? null + const isConnected = moduleFixtures.every( + mf => mf.expectOpentronsModuleSerialNumber + ) + ? attachedModules.some( + m => + m.moduleModel === model && + m.serialNumber === configuredModuleSerialNumber + ) + : true + return { + hardwareType: 'module', + moduleModel: model, + slot: location.slotName, + connected: isConnected, + hasSlotConflict: deckConfig.some( + ({ cutoutId, cutoutFixtureId }) => + cutoutId === getCutoutIdForSlotName(location.slotName, deckDef) && + !getCutoutFixtureIdsForModuleModel(model).includes(cutoutFixtureId) + ), + } + }) + + const requiredPipettes: ProtocolPipette[] = analysis.pipettes.map( + ({ mount, pipetteName }) => ({ + hardwareType: 'pipette', + pipetteName: pipetteName, + mount: mount, + connected: + attachedInstruments.some( + i => + i.instrumentType === 'pipette' && + i.ok && + i.mount === mount && + i.instrumentName === pipetteName + ) ?? false, + }) + ) + + // fixture includes at least 1 required addressableArea AND it doesn't ONLY include a single slot addressableArea + const requiredDeckConfigCompatibility = deckConfigCompatibility.filter( + ({ requiredAddressableAreas }) => { + const atLeastOneAA = requiredAddressableAreas.length > 0 + const notOnlySingleSlot = !( + requiredAddressableAreas.length === 1 && + FLEX_SINGLE_SLOT_ADDRESSABLE_AREAS.includes(requiredAddressableAreas[0]) + ) + return atLeastOneAA && notOnlySingleSlot + } + ) + + const requiredFixtures = requiredDeckConfigCompatibility + // filter out all fixtures that only provide usb module addressable areas + // as they're handled in the requiredModules section via hardwareType === 'module' + .filter( + ({ requiredAddressableAreas }) => + !requiredAddressableAreas.every(modAA => + FLEX_USB_MODULE_ADDRESSABLE_AREAS.includes(modAA) + ) + ) + .map(({ cutoutFixtureId, cutoutId, compatibleCutoutFixtureIds }) => ({ + hardwareType: 'fixture' as const, + cutoutFixtureId: compatibleCutoutFixtureIds[0], + location: { cutout: cutoutId }, + hasSlotConflict: + cutoutFixtureId != null && + !compatibleCutoutFixtureIds.includes(cutoutFixtureId), + })) + + return { + requiredProtocolHardware: [ + ...requiredPipettes, + ...requiredModules, + ...requiredGripper, + ...requiredFixtures, + ], + isLoading: isLoadingInstruments || isLoadingModules, + } +} diff --git a/app/src/transformations/commands/index.ts b/app/src/transformations/commands/index.ts new file mode 100644 index 00000000000..9a6ffc01152 --- /dev/null +++ b/app/src/transformations/commands/index.ts @@ -0,0 +1,2 @@ +export * from './hooks' +export * from './transformations' diff --git a/app/src/pages/Desktop/Protocols/utils/index.ts b/app/src/transformations/commands/transformations/getLabwareSetupItemGroups.ts similarity index 100% rename from app/src/pages/Desktop/Protocols/utils/index.ts rename to app/src/transformations/commands/transformations/getLabwareSetupItemGroups.ts diff --git a/app/src/transformations/commands/transformations/getProtocolUsesGripper.ts b/app/src/transformations/commands/transformations/getProtocolUsesGripper.ts new file mode 100644 index 00000000000..d308e6c29a4 --- /dev/null +++ b/app/src/transformations/commands/transformations/getProtocolUsesGripper.ts @@ -0,0 +1,15 @@ +import type { + ProtocolAnalysisOutput, + CompletedProtocolAnalysis, +} from '@opentrons/shared-data' + +export function getProtocolUsesGripper( + analysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput +): boolean { + return ( + analysis?.commands.some( + c => + c.commandType === 'moveLabware' && c.params.strategy === 'usingGripper' + ) ?? false + ) +} diff --git a/app/src/transformations/commands/transformations/index.ts b/app/src/transformations/commands/transformations/index.ts new file mode 100644 index 00000000000..47b64721bc9 --- /dev/null +++ b/app/src/transformations/commands/transformations/index.ts @@ -0,0 +1,2 @@ +export * from './getLabwareSetupItemGroups' +export * from './getProtocolUsesGripper' From 5e8fbedcb0213677c377c50c70ab7075def62f83 Mon Sep 17 00:00:00 2001 From: Jethary Alcid <66035149+jerader@users.noreply.github.com> Date: Tue, 17 Sep 2024 09:16:15 -0400 Subject: [PATCH 08/24] feat(protocol-designer): display required app version in overview (#16257) closes AUTH-796 --- .../localization/en/protocol_overview.json | 2 ++ .../__tests__/ProtocolOverview.test.tsx | 2 ++ .../src/pages/ProtocolOverview/index.tsx | 16 +++++++++++++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/protocol-designer/src/assets/localization/en/protocol_overview.json b/protocol-designer/src/assets/localization/en/protocol_overview.json index 4e6a445189f..ef978507ab5 100644 --- a/protocol-designer/src/assets/localization/en/protocol_overview.json +++ b/protocol-designer/src/assets/localization/en/protocol_overview.json @@ -1,5 +1,6 @@ { "add_gripper": "Add a gripper", + "app_version": "{{version}} or higher", "author": "Organization/Author", "created": "Date created", "deck_hardware": "Deck hardware", @@ -24,6 +25,7 @@ "no_liquids": "No liquids", "no_steps": "No steps defined", "protocol_metadata": "Protocol Metadata", + "required_app_version": "Required app version", "right_pip": "Right pipette", "robotType": "Robot type", "starting_deck": "Protocol Starting Deck", diff --git a/protocol-designer/src/pages/ProtocolOverview/__tests__/ProtocolOverview.test.tsx b/protocol-designer/src/pages/ProtocolOverview/__tests__/ProtocolOverview.test.tsx index 29137e647dd..82def837501 100644 --- a/protocol-designer/src/pages/ProtocolOverview/__tests__/ProtocolOverview.test.tsx +++ b/protocol-designer/src/pages/ProtocolOverview/__tests__/ProtocolOverview.test.tsx @@ -94,6 +94,8 @@ describe('ProtocolOverview', () => { screen.getByText('mockAuthor') screen.getByText('Date created') screen.getByText('Last exported') + screen.getByText('Required app version') + screen.getByText('8.0.0 or higher') // instruments screen.getByText('Instruments') screen.getByText('Robot type') diff --git a/protocol-designer/src/pages/ProtocolOverview/index.tsx b/protocol-designer/src/pages/ProtocolOverview/index.tsx index 2be14aa9b56..8e261228116 100644 --- a/protocol-designer/src/pages/ProtocolOverview/index.tsx +++ b/protocol-designer/src/pages/ProtocolOverview/index.tsx @@ -58,6 +58,7 @@ import type { DeckSlot } from '@opentrons/step-generation' import type { ThunkDispatch } from '../../types' import type { HintKey } from '../../tutorial' +const REQUIRED_APP_VERSION = '8.0.0' const DATE_ONLY_FORMAT = 'MMMM dd, yyyy' const DATETIME_FORMAT = 'MMMM dd, yyyy | h:mm a' @@ -330,7 +331,11 @@ export function ProtocolOverview(): JSX.Element { - + {metaDataInfo.map(info => { const [title, value] = Object.entries(info)[0] @@ -345,6 +350,15 @@ export function ProtocolOverview(): JSX.Element { ) })} + + + Date: Tue, 17 Sep 2024 13:26:02 -0400 Subject: [PATCH 09/24] feat(protocol-designer): wire up the settings page (#16266) closes AUTH-795 AUTH-794 --- protocol-designer/src/Bouncing.tsx | 135 --------- protocol-designer/src/NavigationBar.tsx | 2 + protocol-designer/src/ProtocolEditor.tsx | 10 +- protocol-designer/src/ProtocolRoutes.tsx | 7 + .../src/__tests__/NavigationBar.test.tsx | 4 + .../src/assets/localization/en/shared.json | 20 +- .../__tests__/SettingsIcon.test.tsx | 38 +++ .../src/molecules/SettingsIcon/index.tsx | 54 ++++ protocol-designer/src/molecules/index.ts | 2 +- .../src/organisms/FeatureFlagsModal/index.tsx | 168 ----------- protocol-designer/src/organisms/index.ts | 1 - .../src/pages/Designer/index.tsx | 5 +- protocol-designer/src/pages/Landing/index.tsx | 2 +- .../Settings/__tests__/Settings.test.tsx | 82 ++++++ .../src/pages/Settings/index.tsx | 264 ++++++++++++++++++ 15 files changed, 473 insertions(+), 321 deletions(-) delete mode 100644 protocol-designer/src/Bouncing.tsx create mode 100644 protocol-designer/src/molecules/SettingsIcon/__tests__/SettingsIcon.test.tsx create mode 100644 protocol-designer/src/molecules/SettingsIcon/index.tsx delete mode 100644 protocol-designer/src/organisms/FeatureFlagsModal/index.tsx create mode 100644 protocol-designer/src/pages/Settings/__tests__/Settings.test.tsx create mode 100644 protocol-designer/src/pages/Settings/index.tsx diff --git a/protocol-designer/src/Bouncing.tsx b/protocol-designer/src/Bouncing.tsx deleted file mode 100644 index fd1dbc74ef2..00000000000 --- a/protocol-designer/src/Bouncing.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import * as React from 'react' -import { css } from 'styled-components' -import { Box, COLORS, POSITION_ABSOLUTE } from '@opentrons/components' -import { FeatureFlagsModal } from './organisms' - -interface Location { - x: number - y: number -} - -const initialPosition = { - x: 100, - y: 100, -} - -const initialVelocity = { - x: 2, - y: 2, -} - -export const Bouncing = (): JSX.Element => { - const [isDragging, setIsDragging] = React.useState(false) - const [position, setPosition] = React.useState(initialPosition) - const [velocity, setVelocity] = React.useState(initialVelocity) - const [isPaused, setIsPaused] = React.useState(false) - const [isStopped, setIsStopped] = React.useState(true) - const [showFeatureFlags, setShowFeatureFlags] = React.useState(false) - - const divSize = 50 - - React.useEffect(() => { - if (!isPaused && !isStopped) { - const moveDiv = (): void => { - const screenWidth = window.innerWidth - const screenHeight = window.innerHeight - - setPosition(prevPosition => { - const newX = prevPosition.x + velocity.x - const newY = prevPosition.y + velocity.y - - if (newX <= 0 || newX + divSize >= screenWidth) { - setVelocity(prevVelocity => ({ - ...prevVelocity, - x: prevVelocity.x * -1, - })) - } - if (newY <= 0 || newY + divSize >= screenHeight) { - setVelocity(prevVelocity => ({ - ...prevVelocity, - y: prevVelocity.y * -1, - })) - } - - return { x: newX, y: newY } - }) - } - - const intervalId = setInterval(moveDiv, 10) - - return () => { - clearInterval(intervalId) - } - } - }, [velocity, isPaused, isStopped]) - - const handleMouseEnter = (): void => { - if (!isStopped) { - setIsPaused(true) - } - } - - const handleMouseLeave = (): void => { - if (!isStopped) { - setIsPaused(false) - } - } - - const handleMouseDown = (): void => { - if (isStopped) { - setIsDragging(true) - } - } - - const handleMouseMove = (e: React.MouseEvent): void => { - if (isDragging) { - const newX = e.clientX - divSize / 2 - const newY = e.clientY - divSize / 2 - setPosition({ x: newX, y: newY }) - } - } - - const handleMouseUp = (): void => { - if (isDragging) { - setIsDragging(false) - } - } - - return ( - <> - {showFeatureFlags ? ( - - ) : null} - - { - setShowFeatureFlags(true) - }} - zIndex={4} - position={POSITION_ABSOLUTE} - width="3.125rem" - height="3.125rem" - backgroundColor={COLORS.blue50} - borderRadius="50%" - left={`${position.x}px`} - top={`${position.y}px`} - cursor="pointer" - css={css` - &:hover { - background-color: ${COLORS.blue60}; - } - `} - onMouseEnter={handleMouseEnter} - onMouseLeave={handleMouseLeave} - onMouseDown={handleMouseDown} - onMouseMove={handleMouseMove} - onMouseUp={handleMouseUp} - /> - - ) -} diff --git a/protocol-designer/src/NavigationBar.tsx b/protocol-designer/src/NavigationBar.tsx index cecddf6d0c1..182bda62c51 100644 --- a/protocol-designer/src/NavigationBar.tsx +++ b/protocol-designer/src/NavigationBar.tsx @@ -18,6 +18,7 @@ import { toggleNewProtocolModal } from './navigation/actions' import { actions as loadFileActions } from './load-file' import { BUTTON_LINK_STYLE } from './atoms' import { getHasUnsavedChanges } from './load-file/selectors' +import { SettingsIcon } from './molecules' import type { ThunkDispatch } from './types' export function NavigationBar(): JSX.Element | null { @@ -77,6 +78,7 @@ export function NavigationBar(): JSX.Element | null { + {location.pathname === '/createNew' ? null : } diff --git a/protocol-designer/src/ProtocolEditor.tsx b/protocol-designer/src/ProtocolEditor.tsx index 52d33b6ea77..519b4de5737 100644 --- a/protocol-designer/src/ProtocolEditor.tsx +++ b/protocol-designer/src/ProtocolEditor.tsx @@ -5,10 +5,7 @@ import { HashRouter } from 'react-router-dom' import { useSelector } from 'react-redux' import { HTML5Backend } from 'react-dnd-html5-backend' import { DIRECTION_COLUMN, Flex } from '@opentrons/components' -import { - getEnableRedesign, - getFeatureFlagData, -} from './feature-flags/selectors' +import { getEnableRedesign } from './feature-flags/selectors' import { ComputingSpinner } from './components/ComputingSpinner' import { ConnectedNav } from './containers/ConnectedNav' import { Sidebar } from './containers/ConnectedSidebar' @@ -24,7 +21,6 @@ import { GateModal } from './components/modals/GateModal' import { CreateFileWizard } from './components/modals/CreateFileWizard' import { AnnouncementModal } from './organisms' import { ProtocolRoutes } from './ProtocolRoutes' -import { Bouncing } from './Bouncing' import styles from './components/ProtocolEditor.module.css' import './css/reset.module.css' @@ -33,17 +29,13 @@ const showGateModal = process.env.NODE_ENV === 'production' || process.env.OT_PD_SHOW_GATE function ProtocolEditorComponent(): JSX.Element { - const flags = useSelector(getFeatureFlagData) const enableRedesign = useSelector(getEnableRedesign) - const prereleaseModeEnabled = flags.PRERELEASE_MODE === true - return (
{enableRedesign ? ( - {prereleaseModeEnabled ? : null} diff --git a/protocol-designer/src/ProtocolRoutes.tsx b/protocol-designer/src/ProtocolRoutes.tsx index 0d0edd5df5b..6917f4ef69f 100644 --- a/protocol-designer/src/ProtocolRoutes.tsx +++ b/protocol-designer/src/ProtocolRoutes.tsx @@ -7,6 +7,7 @@ import { Liquids } from './pages/Liquids' import { Designer } from './pages/Designer' import { CreateNewProtocolWizard } from './pages/CreateNewProtocolWizard' import { NavigationBar } from './NavigationBar' +import { Settings } from './pages/Settings' import { Kitchen, FileUploadMessagesModal, @@ -41,6 +42,12 @@ const pdRoutes: RouteProps[] = [ navLinkTo: '/createNew', path: '/createNew', }, + { + Component: Settings, + name: 'Settings', + navLinkTo: '/settings', + path: '/settings', + }, ] export function ProtocolRoutes(): JSX.Element { diff --git a/protocol-designer/src/__tests__/NavigationBar.test.tsx b/protocol-designer/src/__tests__/NavigationBar.test.tsx index 1bd852474e8..322181be251 100644 --- a/protocol-designer/src/__tests__/NavigationBar.test.tsx +++ b/protocol-designer/src/__tests__/NavigationBar.test.tsx @@ -8,7 +8,9 @@ import { renderWithProviders } from '../__testing-utils__' import { NavigationBar } from '../NavigationBar' import { getHasUnsavedChanges } from '../load-file/selectors' import { toggleNewProtocolModal } from '../navigation/actions' +import { SettingsIcon } from '../molecules' +vi.mock('../molecules') vi.mock('../navigation/actions') vi.mock('../file-data/selectors') vi.mock('../load-file/selectors') @@ -24,6 +26,7 @@ const render = () => { describe('NavigationBar', () => { beforeEach(() => { vi.mocked(getHasUnsavedChanges).mockReturnValue(false) + vi.mocked(SettingsIcon).mockReturnValue(
mock SettingsIcon
) }) it('should render text and link button', () => { render() @@ -32,6 +35,7 @@ describe('NavigationBar', () => { screen.getByText('Version # fake_PD_version') screen.getByText('Create new') screen.getByText('Import') + screen.getByText('mock SettingsIcon') }) it('when clicking Create new, should call the toggle action', () => { diff --git a/protocol-designer/src/assets/localization/en/shared.json b/protocol-designer/src/assets/localization/en/shared.json index 77be26bec0f..afe8f157fa5 100644 --- a/protocol-designer/src/assets/localization/en/shared.json +++ b/protocol-designer/src/assets/localization/en/shared.json @@ -1,6 +1,7 @@ { "add": "add", "amount": "Amount:", + "app_settings": "App settings", "ask_for_labware_overwrite": "Duplicate labware name", "cancel": "Cancel", "close": "Close", @@ -9,27 +10,29 @@ "confirm": "Confirm", "create_a_protocol": "Create a protocol", "create_new": "Create new", + "developer_ff": "Developer feature flags", "done": "Done", "edit_existing": "Edit existing protocol", - "edit": "edit", - "edit_protocol_metadata": "Edit protocol metadata", "edit_instruments": "Edit instruments", + "edit_protocol_metadata": "Edit protocol metadata", + "edit": "edit", "eight_channel": "8-Channel", "exact_labware_match": "Duplicate labware definition", "exit": "exit", "fixtures": "Fixtures", "go_back": "Go back", "gripper": "Gripper", + "heatershakermoduletype": "Heater-shaker Module", + "hints": "Hints", "import": "Import", "incorrect_file_header": "Invalid file type", "incorrect_file_type_body": "Protocol Designer only accepts JSON protocol files created with Protocol Designer. Upload a valid file to continue.", "invalid_json_file_body": "This JSON file is either missing required information or contains sections that Protocol Designer cannot read. At this time we do not support JSON files created outside of Protocol Designer.", "invalid_json_file_error": "Error message:", "invalid_json_file": "Invalid JSON file", - "heatershakermoduletype": "Heater-shaker Module", + "labware_detail": "Labware detail", "labware_name_conflict": "Duplicate labware name", "labware": "Labware", - "labware_detail": "Labware detail", "left_right": "Left+Right", "left": "Left", "liquid": "Liquid", @@ -76,6 +79,7 @@ "module": "Module", "next": "next", "ninety_six_channel": "96-Channel", + "no_hints_to_restore": "No hints to restore", "no-code-required": "The easiest way to automate liquid handling on your Opentrons robot. No code required.", "no": "No", "none": "None", @@ -87,13 +91,19 @@ "ot2": "Opentrons OT-2", "overwrite_labware": "Overwrite labware", "overwrite": "Click Overwrite to replace the existing labware with the new labware.", + "pd_version": "Protocol designer version", + "privacy": "Privacy", "protocol_designer": "Protocol Designer", "re_export": "To use this definition, use Labware Creator to give it a unique load name and display name.", "remove": "remove", + "reset_hints_and_tips": "Reset all hints and tips notifications", + "reset_hints": "Reset hints", "right": "Right", "save": "Save", + "settings": "Settings", "shared_display_name": "Shared display name: ", "shared_load_name": "Shared load name: ", + "shared_sessions": "Share sessions with Opentrons", "shares_name": "This labware has the same load name or display name as {{customOrStandard}}, which is already in this protocol.", "slot_detail": "Slot Detail", "stagingArea": "Staging area", @@ -102,11 +112,13 @@ "temperaturemoduletype": "Temperature Module", "thermocyclermoduletype": "Thermocycler Module", "trashBin": "Trash Bin", + "user_settings": "User settings", "uses_standard_namespace": "Opentrons verified labware", "version": "Version # {{version}}", "warning": "WARNING:", "wasteChute": "Waste chute", "wasteChuteAndStagingArea": "Waste chute and staging area slot", + "we_are_improving": "We’re working to improve Protocol Designer. Part of the process involves watching real user sessions to understand which parts of the interface are working and which could use improvement. We never share sessions outside of Opentrons.", "welcome": "Welcome to Protocol Designer", "yes": "Yes" } diff --git a/protocol-designer/src/molecules/SettingsIcon/__tests__/SettingsIcon.test.tsx b/protocol-designer/src/molecules/SettingsIcon/__tests__/SettingsIcon.test.tsx new file mode 100644 index 00000000000..5ec0f40d48d --- /dev/null +++ b/protocol-designer/src/molecules/SettingsIcon/__tests__/SettingsIcon.test.tsx @@ -0,0 +1,38 @@ +import * as React from 'react' +import { describe, it, vi, beforeEach, expect } from 'vitest' +import { fireEvent, screen } from '@testing-library/react' +import { renderWithProviders } from '../../../__testing-utils__' +import { getFileMetadata } from '../../../file-data/selectors' +import { SettingsIcon } from '..' +import type { NavigateFunction } from 'react-router-dom' + +const mockNavigate = vi.fn() + +vi.mock('react-router-dom', async importOriginal => { + const reactRouterDom = await importOriginal() + return { + ...reactRouterDom, + useNavigate: () => mockNavigate, + useLocation: () => ({ + location: { + pathname: '/settings', + }, + }), + } +}) +vi.mock('../../../file-data/selectors') + +const render = () => { + return renderWithProviders()[0] +} + +describe('SettingsIcon', () => { + beforeEach(() => { + vi.mocked(getFileMetadata).mockReturnValue({}) + }) + it('renders the SettingsIcon', () => { + render() + fireEvent.click(screen.getByTestId('SettingsIconButton')) + expect(mockNavigate).toHaveBeenCalled() + }) +}) diff --git a/protocol-designer/src/molecules/SettingsIcon/index.tsx b/protocol-designer/src/molecules/SettingsIcon/index.tsx new file mode 100644 index 00000000000..accf1fa5720 --- /dev/null +++ b/protocol-designer/src/molecules/SettingsIcon/index.tsx @@ -0,0 +1,54 @@ +import * as React from 'react' +import { useSelector } from 'react-redux' +import { useLocation, useNavigate } from 'react-router-dom' +import { + BORDERS, + Btn, + COLORS, + Flex, + Icon, + JUSTIFY_CENTER, +} from '@opentrons/components' +import { getFileMetadata } from '../../file-data/selectors' +import { BUTTON_LINK_STYLE } from '../../atoms/constants' + +// TODO(ja): this icon needs to be updated to match css states and correct svg +export const SettingsIcon = (): JSX.Element => { + const location = useLocation() + const navigate = useNavigate() + const metadata = useSelector(getFileMetadata) + + const handleNavigate = (): void => { + if (metadata?.created != null && location.pathname === '/settings') { + navigate(-1) + } else if (location.pathname !== '/settings') { + navigate('/settings') + } else { + navigate('/') + } + } + + return ( + + + + + + + + ) +} diff --git a/protocol-designer/src/molecules/index.ts b/protocol-designer/src/molecules/index.ts index a865b38e935..ae458bce67b 100644 --- a/protocol-designer/src/molecules/index.ts +++ b/protocol-designer/src/molecules/index.ts @@ -1 +1 @@ -console.log('molecules for new components') +export * from './SettingsIcon' diff --git a/protocol-designer/src/organisms/FeatureFlagsModal/index.tsx b/protocol-designer/src/organisms/FeatureFlagsModal/index.tsx deleted file mode 100644 index 22484409642..00000000000 --- a/protocol-designer/src/organisms/FeatureFlagsModal/index.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import * as React from 'react' -import { createPortal } from 'react-dom' -import { useTranslation } from 'react-i18next' -import { useDispatch, useSelector } from 'react-redux' -import { css } from 'styled-components' -import { - Btn, - COLORS, - DIRECTION_COLUMN, - Flex, - Icon, - JUSTIFY_END, - JUSTIFY_SPACE_BETWEEN, - Modal, - PrimaryButton, - SPACING, - SecondaryButton, - StyledText, -} from '@opentrons/components' -import { getFeatureFlagData } from '../../feature-flags/selectors' -import { getTopPortalEl } from '../../components/portals/TopPortal' -import { actions as featureFlagActions } from '../../feature-flags' - -import type { FlagTypes } from '../../feature-flags' - -interface FeatureFlagsModalProps { - setShowFeatureFlags: React.Dispatch> - setIsPaused: React.Dispatch> - setIsStopped: React.Dispatch> - isStopped: boolean -} -export function FeatureFlagsModal(props: FeatureFlagsModalProps): JSX.Element { - const { setShowFeatureFlags, setIsPaused, setIsStopped, isStopped } = props - const { t } = useTranslation(['feature_flags', 'shared']) - const flags = useSelector(getFeatureFlagData) - const dispatch = useDispatch() - const allFlags = Object.keys(flags) as FlagTypes[] - - const getDescription = (flag: FlagTypes): string => { - return flag === 'OT_PD_DISABLE_MODULE_RESTRICTIONS' - ? t(`feature_flags:${flag}.description_1`) - : t(`feature_flags:${flag}.description`) - } - - const setFeatureFlags = ( - flags: Partial> - ): void => { - dispatch(featureFlagActions.setFeatureFlags(flags)) - } - - const toFlagRow = (flagName: FlagTypes): JSX.Element => { - const iconName = Boolean(flags[flagName]) - ? 'ot-toggle-input-on' - : 'ot-toggle-input-off' - - return ( - - - - {t(`feature_flags:${flagName}.title`)} - - - {getDescription(flagName)} - - - { - setFeatureFlags({ - [flagName as string]: !flags[flagName], - }) - }} - > - - - - ) - } - - const prereleaseFlagRows = allFlags.map(toFlagRow) - - return createPortal( - { - setShowFeatureFlags(false) - setIsPaused(false) - }} - footer={ - - { - setIsStopped(prev => !prev) - setIsPaused(false) - }} - > - {isStopped ? 'Resume bounce' : 'Stop ball from bouncing'} - - { - setShowFeatureFlags(false) - }} - > - {t('shared:close')} - - - } - closeOnOutsideClick - > - - For internal use only. - - - {prereleaseFlagRows} - - , - getTopPortalEl() - ) -} - -const TOGGLE_DISABLED_STYLES = css` - color: ${COLORS.grey50}; - - &:hover { - color: ${COLORS.grey55}; - } - - &:focus-visible { - box-shadow: 0 0 0 3px ${COLORS.yellow50}; - } - - &:disabled { - color: ${COLORS.grey30}; - } -` - -const TOGGLE_ENABLED_STYLES = css` - color: ${COLORS.blue50}; - - &:hover { - color: ${COLORS.blue55}; - } - - &:focus-visible { - box-shadow: 0 0 0 3px ${COLORS.yellow50}; - } - - &:disabled { - color: ${COLORS.grey30}; - } -` diff --git a/protocol-designer/src/organisms/index.ts b/protocol-designer/src/organisms/index.ts index a72d6865981..f2ba204e3bb 100644 --- a/protocol-designer/src/organisms/index.ts +++ b/protocol-designer/src/organisms/index.ts @@ -4,7 +4,6 @@ export * from './DefineLiquidsModal' export * from './EditInstrumentsModal' export * from './EditNickNameModal' export * from './EditProtocolMetadataModal' -export * from './FeatureFlagsModal' export * from './FileUploadMessagesModal/' export * from './IncompatibleTipsModal' export * from './Kitchen' diff --git a/protocol-designer/src/pages/Designer/index.tsx b/protocol-designer/src/pages/Designer/index.tsx index 3e9df7f11ee..b246ad3a503 100644 --- a/protocol-designer/src/pages/Designer/index.tsx +++ b/protocol-designer/src/pages/Designer/index.tsx @@ -23,6 +23,7 @@ import { useKitchen } from '../../organisms/Kitchen/hooks' import { getDeckSetupForActiveItem } from '../../top-selectors/labware-locations' import { generateNewProtocol } from '../../labware-ingred/actions' import { DefineLiquidsModal, ProtocolMetadataNav } from '../../organisms' +import { SettingsIcon } from '../../molecules' import { DeckSetupContainer } from './DeckSetup' import { selectors } from '../../labware-ingred/selectors' import { OffDeck } from './Offdeck' @@ -146,7 +147,8 @@ export function Designer(): JSX.Element { )} - + + { showLiquidOverflowMenu(true) @@ -159,7 +161,6 @@ export function Designer(): JSX.Element { - { if (hasTrashEntity) { diff --git a/protocol-designer/src/pages/Landing/index.tsx b/protocol-designer/src/pages/Landing/index.tsx index 47674bddda3..d0fe4acdbca 100644 --- a/protocol-designer/src/pages/Landing/index.tsx +++ b/protocol-designer/src/pages/Landing/index.tsx @@ -45,7 +45,7 @@ export function Landing(): JSX.Element { flexDirection={DIRECTION_COLUMN} alignItems={ALIGN_CENTER} paddingTop="14.875rem" - height="calc(100vh - 48px)" + height="calc(100vh - 56px)" width="100%" > { + return renderWithProviders( + + + , + { + i18nInstance: i18n, + } + )[0] +} + +describe('Settings', () => { + beforeEach(() => { + vi.mocked(getHasOptedIn).mockReturnValue(false) + vi.mocked(getFeatureFlagData).mockReturnValue({}) + vi.mocked(getCanClearHintDismissals).mockReturnValue(true) + }) + it('renders the settings page without the dev ffs visible', () => { + render() + screen.getByText('Settings') + screen.getByText('App settings') + screen.getByText('Protocol designer version') + screen.getByText('fake_PD_version') + screen.getByText('User settings') + screen.getByText('Hints') + screen.getByText('Reset all hints and tips notifications') + screen.getByText('Reset hints') + screen.getByText('Privacy') + screen.getByText('Share sessions with Opentrons') + screen.getByText( + 'We’re working to improve Protocol Designer. Part of the process involves watching real user sessions to understand which parts of the interface are working and which could use improvement. We never share sessions outside of Opentrons.' + ) + }) + it('renders the hints button and calls to dismiss them when text is pressed', () => { + render() + fireEvent.click(screen.getByText('Reset hints')) + expect(vi.mocked(clearAllHintDismissals)).toHaveBeenCalled() + }) + it('renders the analytics toggle and calls the action when pressed', () => { + render() + fireEvent.click(screen.getByTestId('analyticsToggle')) + expect(vi.mocked(optIn)).toHaveBeenCalled() + }) + it('renders the dev ffs section when prerelease mode is turned on', () => { + vi.mocked(getFeatureFlagData).mockReturnValue({ + PRERELEASE_MODE: true, + OT_PD_DISABLE_MODULE_RESTRICTIONS: true, + }) + + render() + screen.getByText('Developer feature flags') + screen.getByText('Use prerelease mode') + screen.getByText('Show in-progress features for testing & internal use') + screen.getByText('Disable module placement restrictions') + screen.getByText( + 'Turn off all restrictions on module placement and related pipette crash guidance.' + ) + fireEvent.click(screen.getByTestId('btn_PRERELEASE_MODE')) + expect(vi.mocked(setFeatureFlags)).toHaveBeenCalled() + }) +}) diff --git a/protocol-designer/src/pages/Settings/index.tsx b/protocol-designer/src/pages/Settings/index.tsx new file mode 100644 index 00000000000..2baa3167ba9 --- /dev/null +++ b/protocol-designer/src/pages/Settings/index.tsx @@ -0,0 +1,264 @@ +import * as React from 'react' +import { useTranslation } from 'react-i18next' +import { useDispatch, useSelector } from 'react-redux' +import { css } from 'styled-components' +import { + ALIGN_CENTER, + BORDERS, + Btn, + COLORS, + DIRECTION_COLUMN, + Flex, + Icon, + JUSTIFY_SPACE_BETWEEN, + SPACING, + StyledText, + TYPOGRAPHY, +} from '@opentrons/components' +import { + actions as analyticsActions, + selectors as analyticsSelectors, +} from '../../analytics' +import { + actions as tutorialActions, + selectors as tutorialSelectors, +} from '../../tutorial' +import { actions as featureFlagActions } from '../../feature-flags' +import { getFeatureFlagData } from '../../feature-flags/selectors' +import type { FlagTypes } from '../../feature-flags' + +export function Settings(): JSX.Element { + const dispatch = useDispatch() + const { t } = useTranslation(['feature_flags', 'shared']) + const hasOptedIn = useSelector(analyticsSelectors.getHasOptedIn) + const flags = useSelector(getFeatureFlagData) + const canClearHintDismissals = useSelector( + tutorialSelectors.getCanClearHintDismissals + ) + const _toggleOptedIn = hasOptedIn + ? analyticsActions.optOut + : analyticsActions.optIn + + const prereleaseModeEnabled = flags.PRERELEASE_MODE === true + + const allFlags = Object.keys(flags) as FlagTypes[] + + const getDescription = (flag: FlagTypes): string => { + return flag === 'OT_PD_DISABLE_MODULE_RESTRICTIONS' + ? t(`feature_flags:${flag}.description_1`) + : t(`feature_flags:${flag}.description`) + } + + const setFeatureFlags = ( + flags: Partial> + ): void => { + dispatch(featureFlagActions.setFeatureFlags(flags)) + } + + const toFlagRow = (flagName: FlagTypes): JSX.Element => { + const iconName = Boolean(flags[flagName]) + ? 'ot-toggle-input-on' + : 'ot-toggle-input-off' + + return ( + + + + {t(`feature_flags:${flagName}.title`)} + + + {getDescription(flagName)} + + + { + setFeatureFlags({ + [flagName as string]: !flags[flagName], + }) + }} + > + + + + ) + } + + const prereleaseFlagRows = allFlags.map(toFlagRow) + + return ( + + + + {t('shared:settings')} + + + + {t('shared:app_settings')} + + + + {t('shared:pd_version')} + + + {process.env.OT_PD_VERSION} + + + + + + {t('shared:user_settings')} + + + + + {t('shared:hints')} + + + + {t('shared:reset_hints_and_tips')} + + + + dispatch(tutorialActions.clearAllHintDismissals())} + > + + {canClearHintDismissals + ? t('shared:reset_hints') + : t('shared:no_hints_to_restore')} + + + + + + + {t('shared:privacy')} + + + + + {t('shared:shared_sessions')} + + + + {t('shared:we_are_improving')} + + + + dispatch(_toggleOptedIn())} + > + + + + + {prereleaseModeEnabled ? ( + + + {t('shared:developer_ff')} + + + {prereleaseFlagRows} + + + ) : null} + + + ) +} + +const TOGGLE_DISABLED_STYLES = css` + color: ${COLORS.grey50}; + + &:hover { + color: ${COLORS.grey55}; + } + + &:focus-visible { + box-shadow: 0 0 0 3px ${COLORS.yellow50}; + } + + &:disabled { + color: ${COLORS.grey30}; + } +` + +const TOGGLE_ENABLED_STYLES = css` + color: ${COLORS.blue50}; + + &:hover { + color: ${COLORS.blue55}; + } + + &:focus-visible { + box-shadow: 0 0 0 3px ${COLORS.yellow50}; + } + + &:disabled { + color: ${COLORS.grey30}; + } +` From c07db362cb17ac5a3c58c6fc915e4f9e5d3ec1ee Mon Sep 17 00:00:00 2001 From: koji Date: Tue, 17 Sep 2024 13:33:14 -0400 Subject: [PATCH 10/24] fix(components): fix largebutton enlarge issue (#16269) * fix(components): fix largebutton enlarge issue --- components/src/atoms/buttons/LargeButton.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/src/atoms/buttons/LargeButton.tsx b/components/src/atoms/buttons/LargeButton.tsx index c880318e18d..9476a7d693d 100644 --- a/components/src/atoms/buttons/LargeButton.tsx +++ b/components/src/atoms/buttons/LargeButton.tsx @@ -213,7 +213,7 @@ export function LargeButton(props: LargeButtonProps): JSX.Element { padding: ${SPACING.spacing24}; line-height: ${TYPOGRAPHY.lineHeight20}; gap: ${SPACING.spacing60}; - border: ${BORDERS.borderRadius4} solid + outline: ${BORDERS.borderRadius4} solid ${ buttonType === 'alertStroke' && !disabled ? LARGE_BUTTON_PROPS_BY_TYPE[buttonType].defaultColor @@ -237,7 +237,7 @@ export function LargeButton(props: LargeButtonProps): JSX.Element { : LARGE_BUTTON_PROPS_BY_TYPE[buttonType].activeBackgroundColor }; ${!disabled && activeColorFor(buttonType)}; - border: ${BORDERS.borderRadius4} solid + outline: ${BORDERS.borderRadius4} solid ${ disabled ? LARGE_BUTTON_PROPS_BY_TYPE[buttonType].disabledBackgroundColor From f3558f45ce76bdbe85a289430c4b21316a01d0ef Mon Sep 17 00:00:00 2001 From: syao1226 <146495172+syao1226@users.noreply.github.com> Date: Tue, 17 Sep 2024 15:02:25 -0400 Subject: [PATCH 11/24] fix(protocol-designer): add swap pipettes icon (#16270) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix RQA-3189 # Overview Adding a swap icon to the swap pipettes button in both `SelectPipettes` and `EditInstrumentsModal`. ![Screenshot 2024-09-17 at 2 44 46 PM](https://github.com/user-attachments/assets/a36540b4-362d-43ce-82a2-2a1bdba0b955) ![Screenshot 2024-09-17 at 2 05 52 PM](https://github.com/user-attachments/assets/828e526f-1216-467a-bcbd-92987e68a084) ## Test Plan and Hands on Testing ## Changelog - Added an Icon with the name `swap-horizontal` and rotate it 90 deg because I couldn't find a `swap-vertical `icon. - Changed the swap pipettes desktop text style from `bodyDefaultRegular` to `captionSemiBold` in `SelectPipettes`. ## Review requests ## Risk assessment --------- Co-authored-by: shiyaochen --- .../src/organisms/EditInstrumentsModal/index.tsx | 9 ++++++++- .../CreateNewProtocolWizard/SelectPipettes.tsx | 15 ++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/protocol-designer/src/organisms/EditInstrumentsModal/index.tsx b/protocol-designer/src/organisms/EditInstrumentsModal/index.tsx index 45282a3a452..fbaa2fb8954 100644 --- a/protocol-designer/src/organisms/EditInstrumentsModal/index.tsx +++ b/protocol-designer/src/organisms/EditInstrumentsModal/index.tsx @@ -11,10 +11,12 @@ import { Checkbox, COLORS, DIRECTION_COLUMN, + DIRECTION_ROW, DISPLAY_FLEX, DISPLAY_INLINE_BLOCK, EmptySelectorButton, Flex, + Icon, JUSTIFY_END, JUSTIFY_SPACE_BETWEEN, ListItem, @@ -218,7 +220,12 @@ export function EditInstrumentsModal( ) } > - + + {t('swap')} diff --git a/protocol-designer/src/pages/CreateNewProtocolWizard/SelectPipettes.tsx b/protocol-designer/src/pages/CreateNewProtocolWizard/SelectPipettes.tsx index 5ed48732bc0..6f95166d268 100644 --- a/protocol-designer/src/pages/CreateNewProtocolWizard/SelectPipettes.tsx +++ b/protocol-designer/src/pages/CreateNewProtocolWizard/SelectPipettes.tsx @@ -13,10 +13,12 @@ import { Btn, Checkbox, DIRECTION_COLUMN, + DIRECTION_ROW, DISPLAY_FLEX, DISPLAY_INLINE_BLOCK, EmptySelectorButton, Flex, + Icon, JUSTIFY_SPACE_BETWEEN, PRODUCT, RadioButton, @@ -400,9 +402,16 @@ export function SelectPipettes(props: WizardTileProps): JSX.Element | null { ) }} > - - {t('swap')} - + + + + {t('swap')} + + )} From 0159c5ebb77a236a8f5a7f536ff5c6141eecab24 Mon Sep 17 00:00:00 2001 From: Brent Hagen Date: Tue, 17 Sep 2024 15:39:12 -0400 Subject: [PATCH 12/24] test(app): add tests for anonymous pipette and gripper display names (#16256) adds tests for OEM mode anonymous pipette and gripper display name hooks closes PLAT-295 --- .../instruments/__tests__/hooks.test.ts | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 app/src/resources/instruments/__tests__/hooks.test.ts diff --git a/app/src/resources/instruments/__tests__/hooks.test.ts b/app/src/resources/instruments/__tests__/hooks.test.ts new file mode 100644 index 00000000000..ab21c81525d --- /dev/null +++ b/app/src/resources/instruments/__tests__/hooks.test.ts @@ -0,0 +1,178 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { useIsOEMMode } from '../../robot-settings/hooks' + +import { + useGripperDisplayName, + usePipetteModelSpecs, + usePipetteNameSpecs, + usePipetteSpecsV2, +} from '../hooks' + +import type { PipetteV2Specs } from '@opentrons/shared-data' + +vi.mock('../../robot-settings/hooks') + +const BRANDED_P1000_FLEX_DISPLAY_NAME = 'Flex 1-Channel 1000 μL' +const ANONYMOUS_P1000_FLEX_DISPLAY_NAME = '1-Channel 1000 μL' + +const mockP1000V2Specs = { + $otSharedSchema: '#/pipette/schemas/2/pipetteGeometrySchema.json', + availableSensors: { + sensors: ['pressure', 'capacitive', 'environment'], + capacitive: { count: 1 }, + environment: { count: 1 }, + pressure: { count: 1 }, + }, + backCompatNames: [], + backlashDistance: 0.1, + channels: 1, + displayCategory: 'FLEX', + displayName: BRANDED_P1000_FLEX_DISPLAY_NAME, + dropTipConfigurations: { plungerEject: { current: 1, speed: 15 } }, + liquids: { + default: { + $otSharedSchema: '#/pipette/schemas/2/pipetteLiquidPropertiesSchema.json', + defaultTipracks: [ + 'opentrons/opentrons_flex_96_tiprack_1000ul/1', + 'opentrons/opentrons_flex_96_tiprack_200ul/1', + 'opentrons/opentrons_flex_96_tiprack_50ul/1', + 'opentrons/opentrons_flex_96_filtertiprack_1000ul/1', + 'opentrons/opentrons_flex_96_filtertiprack_200ul/1', + 'opentrons/opentrons_flex_96_filtertiprack_50ul/1', + ], + minVolume: 5, + maxVolume: 1000, + supportedTips: expect.anything(), + }, + }, + model: 'p1000', + nozzleMap: expect.anything(), + pathTo3D: + 'pipette/definitions/2/geometry/single_channel/p1000/placeholder.gltf', + validNozzleMaps: { + maps: { + SingleA1: ['A1'], + }, + }, + pickUpTipConfigurations: { + pressFit: { + presses: 1, + increment: 0, + configurationsByNozzleMap: { + SingleA1: { + default: { + speed: 10, + distance: 13, + current: 0.2, + tipOverlaps: { + v0: { + default: 10.5, + 'opentrons/opentrons_flex_96_tiprack_1000ul/1': 9.65, + 'opentrons/opentrons_flex_96_tiprack_200ul/1': 9.76, + 'opentrons/opentrons_flex_96_tiprack_50ul/1': 10.09, + 'opentrons/opentrons_flex_96_filtertiprack_1000ul/1': 9.65, + 'opentrons/opentrons_flex_96_filtertiprack_200ul/1': 9.76, + 'opentrons/opentrons_flex_96_filtertiprack_50ul/1': 10.09, + }, + }, + }, + }, + }, + }, + }, + partialTipConfigurations: { + availableConfigurations: null, + partialTipSupported: false, + }, + plungerHomingConfigurations: { current: 1, speed: 30 }, + plungerMotorConfigurations: { idle: 0.3, run: 1 }, + plungerPositionsConfigurations: { + default: { blowout: 76.5, bottom: 71.5, drop: 90.5, top: 0 }, + }, + quirks: [], + shaftDiameter: 4.5, + shaftULperMM: 15.904, + nozzleOffset: [-8, -22, -259.15], + orderedColumns: expect.anything(), + orderedRows: expect.anything(), + pipetteBoundingBoxOffsets: { + backLeftCorner: [-8, -22, -259.15], + frontRightCorner: [-8, -22, -259.15], + }, + lldSettings: { + t50: { + minHeight: 1.0, + minVolume: 0, + }, + t200: { + minHeight: 1.0, + minVolume: 0, + }, + t1000: { + minHeight: 1.5, + minVolume: 0, + }, + }, +} as PipetteV2Specs + +describe('pipette data accessor hooks', () => { + beforeEach(() => { + vi.mocked(useIsOEMMode).mockReturnValue(false) + }) + + describe('usePipetteNameSpecs', () => { + it('returns the branded display name for P1000 single flex', () => { + expect(usePipetteNameSpecs('p1000_single_flex')?.displayName).toEqual( + BRANDED_P1000_FLEX_DISPLAY_NAME + ) + }) + + it('returns an anonymized display name in OEM mode', () => { + vi.mocked(useIsOEMMode).mockReturnValue(true) + expect(usePipetteNameSpecs('p1000_single_flex')?.displayName).toEqual( + ANONYMOUS_P1000_FLEX_DISPLAY_NAME + ) + }) + }) + + describe('usePipetteModelSpecs', () => { + it('returns the branded display name for P1000 single flex', () => { + expect(usePipetteModelSpecs('p1000_single_v3.6')?.displayName).toEqual( + BRANDED_P1000_FLEX_DISPLAY_NAME + ) + }) + + it('returns an anonymized display name in OEM mode', () => { + vi.mocked(useIsOEMMode).mockReturnValue(true) + expect(usePipetteModelSpecs('p1000_single_v3.6')?.displayName).toEqual( + ANONYMOUS_P1000_FLEX_DISPLAY_NAME + ) + }) + }) + + describe('usePipetteSpecsV2', () => { + it('returns the correct info for p1000_single_flex which should be the latest model version 3.7', () => { + expect(usePipetteSpecsV2('p1000_single_flex')).toStrictEqual( + mockP1000V2Specs + ) + }) + it('returns an anonymized display name in OEM mode', () => { + vi.mocked(useIsOEMMode).mockReturnValue(true) + expect(usePipetteSpecsV2('p1000_single_flex')).toStrictEqual({ + ...mockP1000V2Specs, + displayName: ANONYMOUS_P1000_FLEX_DISPLAY_NAME, + }) + }) + }) + + describe('useGripperDisplayName', () => { + it('returns the branded gripper display name', () => { + expect(useGripperDisplayName('gripperV1.3')).toEqual('Flex Gripper') + }) + it('returns an anonymized display name in OEM mode', () => { + vi.mocked(useIsOEMMode).mockReturnValue(true) + expect(useGripperDisplayName('gripperV1.3')).toEqual('Gripper') + }) + }) +}) From 17ac41541edc94ea8a1bb3270a88ad5acd8aa1ef Mon Sep 17 00:00:00 2001 From: Brent Hagen Date: Tue, 17 Sep 2024 15:39:57 -0400 Subject: [PATCH 13/24] test(app): enforce branded localization rules (#16251) codifies rules for branded copy: 1) branded.json and anonymous.json should have the same translation keys 2) copy in other localization assets should not contain "Opentrons" or "Flex" closes PLAT-267 --- app/src/LocalizationProvider.tsx | 4 +-- .../localization/__tests__/branded.test.ts | 34 +++++++++++++++++++ app/src/assets/localization/en/anonymous.json | 14 ++++---- app/src/assets/localization/en/branded.json | 14 ++++---- .../localization/en/devices_landing.json | 2 -- .../DevicesLanding/NewRobotSetupHelp.tsx | 6 ++-- 6 files changed, 55 insertions(+), 19 deletions(-) create mode 100644 app/src/assets/localization/__tests__/branded.test.ts diff --git a/app/src/LocalizationProvider.tsx b/app/src/LocalizationProvider.tsx index e2a30c95cd7..7fbb30d4774 100644 --- a/app/src/LocalizationProvider.tsx +++ b/app/src/LocalizationProvider.tsx @@ -10,8 +10,8 @@ export interface OnDeviceLocalizationProviderProps { children?: React.ReactNode } -const BRANDED_RESOURCE = 'branded' -const ANONYMOUS_RESOURCE = 'anonymous' +export const BRANDED_RESOURCE = 'branded' +export const ANONYMOUS_RESOURCE = 'anonymous' // TODO(bh, 2024-03-26): anonymization limited to ODD for now, may change in future OEM phases export function OnDeviceLocalizationProvider( diff --git a/app/src/assets/localization/__tests__/branded.test.ts b/app/src/assets/localization/__tests__/branded.test.ts new file mode 100644 index 00000000000..ed53aabbaec --- /dev/null +++ b/app/src/assets/localization/__tests__/branded.test.ts @@ -0,0 +1,34 @@ +import { describe, it, expect } from 'vitest' +import { + ANONYMOUS_RESOURCE, + BRANDED_RESOURCE, +} from '../../../LocalizationProvider' +import { resources } from '..' + +describe('branded copy', () => { + it('branded and anonymous resources contain the same translation keys', () => { + const brandedKeys = Object.keys(resources.en[BRANDED_RESOURCE]) + const anonymousKeys = Object.keys(resources.en[ANONYMOUS_RESOURCE]) + + brandedKeys.forEach((brandedKey, i) => { + const anonymousKey = anonymousKeys[i] + expect(brandedKey).toEqual(anonymousKey) + }) + }) + + it('non-branded copy does not contain "Opentrons" or "Flex"', () => { + const nonBrandedResources = Object.entries(resources.en).filter( + resource => + resource[0] !== BRANDED_RESOURCE && resource[0] !== ANONYMOUS_RESOURCE + ) + + const nonBrandedCopy = nonBrandedResources + .map(resource => Object.values(resource[1])) + .flat() + + nonBrandedCopy.forEach(phrase => { + expect(phrase.match(/opentrons/i)).toBeNull() + expect(phrase.match(/flex/i)).toBeNull() + }) + }) +}) diff --git a/app/src/assets/localization/en/anonymous.json b/app/src/assets/localization/en/anonymous.json index 12e57d595fa..221dfd72400 100644 --- a/app/src/assets/localization/en/anonymous.json +++ b/app/src/assets/localization/en/anonymous.json @@ -2,8 +2,8 @@ "a_robot_software_update_is_available": "A robot software update is required to run protocols with this version of the desktop app. Go to Robot", "about_flex_gripper": "About Gripper", "alternative_security_types_description": "The robot supports connecting to various enterprise access points. Connect via USB and finish setup in the desktop app.", - "attach_a_pipette": "Attach a pipette to your robot", "attach_a_pipette_for_quick_transfer": "To create a quick transfer, you need to attach a pipette to your robot.", + "attach_a_pipette": "Attach a pipette to your robot", "calibration_block_description": "This block is a specially made tool that fits perfectly on your deck and helps with calibration.If you do not have a Calibration Block, please email support so we can send you one. In your message, be sure to include your name, company or institution name, and shipping address. While you wait for the block to arrive, you can use the flat surface on the trash bin of your robot instead.", "calibration_on_opentrons_tips_is_important": "It’s extremely important to perform this calibration using the tips and tip racks specified above, as the robot determines accuracy based on the known measurements of these tips.", "choose_what_data_to_share": "Choose what robot data to share.", @@ -37,17 +37,19 @@ "module_calibration_get_started": "To get started, remove labware from the deck and clean up the working area to make the calibration easier. Also gather the needed equipment shown to the right.The calibration adapter came with your module. The pipette probe came with your pipette.", "module_error_contact_support": "Try powering the module off and on again. If the error persists, contact support.", "network_setup_menu_description": "You’ll use this connection to run software updates and load protocols onto your robot.", + "new_robot_instructions": "When setting up a new robot, follow the instructions on the touchscreen. For more information, consult the Quickstart Guide for your robot.", "oem_mode_description": "Enable OEM Mode to remove all instances of Opentrons from the Flex touchscreen.", "opentrons_app_successfully_updated": "The app was successfully updated.", - "opentrons_app_update": "app update", - "opentrons_app_update_available": "App Update Available", "opentrons_app_update_available_variation": "An app update is available.", + "opentrons_app_update_available": "App Update Available", + "opentrons_app_update": "app update", "opentrons_app_will_use_interpreter": "If specified, the app will use the Python interpreter at this path instead of the default bundled Python interpreter.", "opentrons_cares_about_privacy": "We care about your privacy. We anonymize all data and only use it to improve our products.", "opentrons_def": "Verified Definition", + "opentrons_flex_quickstart_guide": "Quickstart Guide", "opentrons_labware_def": "Verified labware definition", - "opentrons_tip_racks_recommended": "Opentrons tip racks are highly recommended. Accuracy cannot be guaranteed with other tip racks.", "opentrons_tip_rack_name": "opentrons", + "opentrons_tip_racks_recommended": "Opentrons tip racks are highly recommended. Accuracy cannot be guaranteed with other tip racks.", "previous_releases": "View previous releases", "receive_alert": "Receive an alert when a software update is available.", "restore_description": "Reverting to previous software versions is not recommended, but you can access previous releases below. For best results, uninstall the existing app and remove its configuration files before installing the previous version.", @@ -58,11 +60,11 @@ "secure_labware_explanation_thermocycler": "Secure your labware to the Thermocycler Module by closing its latch. Doing so ensures level and accurate plate placement for optimal results.", "send_a_protocol_to_store": "Send a protocol to the robot to get started.", "setup_instructions_description": "For step-by-step instructions on setting up your module, consult the Quickstart Guide that came in its box.", - "share_app_analytics": "Share App Analytics", "share_app_analytics_description": "Help improve this product by automatically sending anonymous diagnostics and usage data.", + "share_app_analytics": "Share App Analytics", "share_display_usage_description": "Data on how you interact with the robot's touchscreen.", - "share_logs_with_opentrons": "Share robot logs", "share_logs_with_opentrons_description": "Help improve this product by automatically sending anonymous robot logs. These logs are used to troubleshoot robot issues and spot error trends.", + "share_logs_with_opentrons": "Share robot logs", "show_labware_offset_snippets_description": "Only for users who need to apply labware offset data outside of the app. When enabled, code snippets for Jupyter Notebook and SSH are available during protocol setup.", "something_seems_wrong": "There may be a problem with your pipette. Exit setup and contact support for assistance.", "storage_limit_reached_description": "Your robot has reached the limit of quick transfers that it can store. You must delete an existing quick transfer before creating a new one.", diff --git a/app/src/assets/localization/en/branded.json b/app/src/assets/localization/en/branded.json index 6a65184183f..2b5f47373e8 100644 --- a/app/src/assets/localization/en/branded.json +++ b/app/src/assets/localization/en/branded.json @@ -1,9 +1,9 @@ { "a_robot_software_update_is_available": "A robot software update is required to run protocols with this version of the Opentrons App. Go to Robot", - "attach_a_pipette": "Attach a pipette to your Flex", - "attach_a_pipette_for_quick_transfer": "To create a quick transfer, you need to attach a pipette to your Opentrons Flex.", "about_flex_gripper": "About Flex Gripper", "alternative_security_types_description": "The Opentrons App supports connecting Flex to various enterprise access points. Connect via USB and finish setup in the app.", + "attach_a_pipette_for_quick_transfer": "To create a quick transfer, you need to attach a pipette to your Opentrons Flex.", + "attach_a_pipette": "Attach a pipette to your Flex", "calibration_block_description": "This block is a specially made tool that fits perfectly on your deck and helps with calibration.If you do not have a Calibration Block, please email support@opentrons.com so we can send you one. In your message, be sure to include your name, company or institution name, and shipping address. While you wait for the block to arrive, you can use the flat surface on the trash bin of your robot instead.", "calibration_on_opentrons_tips_is_important": "It’s extremely important to perform this calibration using the Opentrons tips and tip racks specified above, as the robot determines accuracy based on the known measurements of these tips.", "choose_what_data_to_share": "Choose what data to share with Opentrons.", @@ -37,14 +37,16 @@ "module_calibration_get_started": "To get started, remove labware from the deck and clean up the working area to make the calibration easier. Also gather the needed equipment shown to the right.The calibration adapter came with your module. The pipette probe came with your Flex pipette.", "module_error_contact_support": "Try powering the module off and on again. If the error persists, contact Opentrons Support.", "network_setup_menu_description": "You’ll use this connection to run software updates and load protocols onto your Opentrons Flex.", + "new_robot_instructions": "When setting up a new Flex, follow the instructions on the touchscreen. For more information, consult the Quickstart Guide for your robot.", "oem_mode_description": "Enable OEM Mode to remove all instances of Opentrons from the Flex touchscreen.", "opentrons_app_successfully_updated": "The Opentrons App was successfully updated.", - "opentrons_app_update": "Opentrons App update", - "opentrons_app_update_available": "Opentrons App Update Available", "opentrons_app_update_available_variation": "An Opentrons App update is available.", + "opentrons_app_update_available": "Opentrons App Update Available", + "opentrons_app_update": "Opentrons App update", "opentrons_app_will_use_interpreter": "If specified, the Opentrons App will use the Python interpreter at this path instead of the default bundled Python interpreter.", "opentrons_cares_about_privacy": "Opentrons cares about your privacy. We anonymize all data and only use it to improve our products.", "opentrons_def": "Opentrons Definition", + "opentrons_flex_quickstart_guide": "Opentrons Flex Quickstart Guide", "opentrons_labware_def": "Opentrons labware definition", "opentrons_tip_rack_name": "opentrons", "opentrons_tip_racks_recommended": "Opentrons tip racks are highly recommended. Accuracy cannot be guaranteed with other tip racks.", @@ -58,11 +60,11 @@ "secure_labware_explanation_thermocycler": "Opentrons recommends securing your labware to the Thermocycler Module by closing its latch. Doing so ensures level and accurate plate placement for optimal results.", "send_a_protocol_to_store": "Send a protocol from the Opentrons App to get started.", "setup_instructions_description": "For step-by-step instructions on setting up your module, consult the Quickstart Guide that came in its box or scan the QR code to visit the modules section of the Opentrons Help Center.", - "share_app_analytics": "Share App Analytics with Opentrons", "share_app_analytics_description": "Help Opentrons improve its products and services by automatically sending anonymous diagnostics and usage data.", + "share_app_analytics": "Share App Analytics with Opentrons", "share_display_usage_description": "Data on how you interact with the touchscreen on Flex.", - "share_logs_with_opentrons": "Share Robot logs with Opentrons", "share_logs_with_opentrons_description": "Help Opentrons improve its products and services by automatically sending anonymous robot logs. Opentrons uses these logs to troubleshoot robot issues and spot error trends.", + "share_logs_with_opentrons": "Share Robot logs with Opentrons", "show_labware_offset_snippets_description": "Only for users who need to apply labware offset data outside of the Opentrons App. When enabled, code snippets for Jupyter Notebook and SSH are available during protocol setup.", "something_seems_wrong": "There may be a problem with your pipette. Exit setup and contact Opentrons Support for assistance.", "storage_limit_reached_description": "Your Opentrons Flex has reached the limit of quick transfers that it can store. You must delete an existing quick transfer before creating a new one.", diff --git a/app/src/assets/localization/en/devices_landing.json b/app/src/assets/localization/en/devices_landing.json index dfd92d23030..1ff5ef11fd6 100644 --- a/app/src/assets/localization/en/devices_landing.json +++ b/app/src/assets/localization/en/devices_landing.json @@ -25,11 +25,9 @@ "looking_for_robots": "Looking for robots", "make_sure_robot_is_connected": "Make sure the robot is connected to this computer", "modules": "Modules", - "new_robot_instructions": "When setting up a new Flex, follow the instructions on the touchscreen. For more information, consult the Quickstart Guide for your robot.", "ninety_six_mount": "Left + Right Mount", "no_robots_found": "No robots found", "not_available": "Not available ({{count}})", - "opentrons_flex_quickstart_guide": "Opentrons Flex Quickstart Guide", "ot2_quickstart_guide": "OT-2 Quickstart Guide", "refresh": "Refresh", "restart_the_app": "Restart the app", diff --git a/app/src/pages/Desktop/Devices/DevicesLanding/NewRobotSetupHelp.tsx b/app/src/pages/Desktop/Devices/DevicesLanding/NewRobotSetupHelp.tsx index dad0b24d8b1..a0b54194621 100644 --- a/app/src/pages/Desktop/Devices/DevicesLanding/NewRobotSetupHelp.tsx +++ b/app/src/pages/Desktop/Devices/DevicesLanding/NewRobotSetupHelp.tsx @@ -23,7 +23,7 @@ const NEW_OT2_SETUP_SUPPORT_ARTICLE_HREF = 'https://insights.opentrons.com/hubfs/Products/OT-2/OT-2%20Quick%20Start%20Guide.pdf' export function NewRobotSetupHelp(): JSX.Element { - const { t } = useTranslation(['devices_landing', 'shared']) + const { t } = useTranslation(['devices_landing', 'shared', 'branded']) const [showNewRobotHelpModal, setShowNewRobotHelpModal] = React.useState( false ) @@ -49,13 +49,13 @@ export function NewRobotSetupHelp(): JSX.Element { > - {t('new_robot_instructions')} + {t('branded:new_robot_instructions')} - {t('opentrons_flex_quickstart_guide')} + {t('branded:opentrons_flex_quickstart_guide')} Date: Tue, 17 Sep 2024 15:42:06 -0400 Subject: [PATCH 14/24] fix(protocol-designer): if metadata is lost, navigate to landing (#16272) closes AUTH-823 --- .../src/pages/Designer/__tests__/Designer.test.tsx | 1 + protocol-designer/src/pages/Designer/index.tsx | 11 +++++++++++ .../src/pages/Liquids/__tests__/Liquids.test.tsx | 2 +- protocol-designer/src/pages/Liquids/index.tsx | 3 ++- .../__tests__/ProtocolOverview.test.tsx | 1 + .../src/pages/ProtocolOverview/index.tsx | 9 +++++++++ 6 files changed, 25 insertions(+), 2 deletions(-) diff --git a/protocol-designer/src/pages/Designer/__tests__/Designer.test.tsx b/protocol-designer/src/pages/Designer/__tests__/Designer.test.tsx index 25aab85730b..97ac029d447 100644 --- a/protocol-designer/src/pages/Designer/__tests__/Designer.test.tsx +++ b/protocol-designer/src/pages/Designer/__tests__/Designer.test.tsx @@ -46,6 +46,7 @@ describe('Designer', () => { beforeEach(() => { vi.mocked(getFileMetadata).mockReturnValue({ protocolName: 'mockProtocolName', + created: 123, }) vi.mocked(selectors.getIsNewProtocol).mockReturnValue(true) vi.mocked(getDeckSetupForActiveItem).mockReturnValue({ diff --git a/protocol-designer/src/pages/Designer/index.tsx b/protocol-designer/src/pages/Designer/index.tsx index b246ad3a503..689405e8eba 100644 --- a/protocol-designer/src/pages/Designer/index.tsx +++ b/protocol-designer/src/pages/Designer/index.tsx @@ -24,6 +24,7 @@ import { getDeckSetupForActiveItem } from '../../top-selectors/labware-locations import { generateNewProtocol } from '../../labware-ingred/actions' import { DefineLiquidsModal, ProtocolMetadataNav } from '../../organisms' import { SettingsIcon } from '../../molecules' +import { getFileMetadata } from '../../file-data/selectors' import { DeckSetupContainer } from './DeckSetup' import { selectors } from '../../labware-ingred/selectors' import { OffDeck } from './Offdeck' @@ -46,6 +47,7 @@ export function Designer(): JSX.Element { const { bakeToast, makeSnackbar } = useKitchen() const navigate = useNavigate() const dispatch = useDispatch() + const fileMetadata = useSelector(getFileMetadata) const zoomIn = useSelector(selectors.getZoomedInSlot) const deckSetup = useSelector(getDeckSetupForActiveItem) const isNewProtocol = useSelector(selectors.getIsNewProtocol) @@ -106,6 +108,15 @@ export function Designer(): JSX.Element { } }, []) + React.useEffect(() => { + if (fileMetadata?.created == null) { + console.warn( + 'fileMetadata was refreshed while on the designer page, redirecting to landing page' + ) + navigate('/') + } + }, [fileMetadata]) + const overflowWrapperRef = useOnClickOutside({ onClickOutside: () => { if (!showDefineLiquidModal) { diff --git a/protocol-designer/src/pages/Liquids/__tests__/Liquids.test.tsx b/protocol-designer/src/pages/Liquids/__tests__/Liquids.test.tsx index 6637f98e124..610e1a53540 100644 --- a/protocol-designer/src/pages/Liquids/__tests__/Liquids.test.tsx +++ b/protocol-designer/src/pages/Liquids/__tests__/Liquids.test.tsx @@ -53,7 +53,7 @@ describe('Liquids', () => { it('calls navigate when there is no active labware', () => { vi.mocked(labwareIngredSelectors.getSelectedLabwareId).mockReturnValue(null) render() - expect(mockNavigate).toHaveBeenCalledWith('/overview') + expect(mockNavigate).toHaveBeenCalledWith('/designer') }) it('renders nav and assign liquids modal', () => { diff --git a/protocol-designer/src/pages/Liquids/index.tsx b/protocol-designer/src/pages/Liquids/index.tsx index ea4e58b749b..e3d1f17c115 100644 --- a/protocol-designer/src/pages/Liquids/index.tsx +++ b/protocol-designer/src/pages/Liquids/index.tsx @@ -43,7 +43,8 @@ export function Liquids(): JSX.Element { React.useEffect(() => { if (selectedLabware == null) { - navigate('/overview') + console.warn('selectedLabware was lost, navigate to deisgner page') + navigate('/designer') } }) diff --git a/protocol-designer/src/pages/ProtocolOverview/__tests__/ProtocolOverview.test.tsx b/protocol-designer/src/pages/ProtocolOverview/__tests__/ProtocolOverview.test.tsx index 82def837501..81840da58f0 100644 --- a/protocol-designer/src/pages/ProtocolOverview/__tests__/ProtocolOverview.test.tsx +++ b/protocol-designer/src/pages/ProtocolOverview/__tests__/ProtocolOverview.test.tsx @@ -66,6 +66,7 @@ describe('ProtocolOverview', () => { protocolName: 'mockName', author: 'mockAuthor', description: 'mockDescription', + created: 123, }) vi.mocked(useBlockingHint).mockReturnValue(null) vi.mocked(MaterialsListModal).mockReturnValue( diff --git a/protocol-designer/src/pages/ProtocolOverview/index.tsx b/protocol-designer/src/pages/ProtocolOverview/index.tsx index 8e261228116..3512db95f3a 100644 --- a/protocol-designer/src/pages/ProtocolOverview/index.tsx +++ b/protocol-designer/src/pages/ProtocolOverview/index.tsx @@ -117,6 +117,15 @@ export function ProtocolOverview(): JSX.Element { typeof leftString | typeof rightString >(leftString) + React.useEffect(() => { + if (formValues?.created == null) { + console.warn( + 'formValues was refreshed while on the overview page, redirecting to landing page' + ) + navigate('/') + } + }, [formValues]) + const { modules: modulesOnDeck, labware: labwaresOnDeck, From 3c376504a63eb40aaf9ac4ff3b17138203ca4c97 Mon Sep 17 00:00:00 2001 From: syao1226 <146495172+syao1226@users.noreply.github.com> Date: Tue, 17 Sep 2024 15:52:49 -0400 Subject: [PATCH 15/24] fix(protocol-designer): add gridGap between buttons (#16275) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix RQA-3188 # Overview Adding spaces between cancel and save button on edit metadata modal. ## Test Plan and Hands on Testing ![Screenshot 2024-09-17 at 3 34 45 PM](https://github.com/user-attachments/assets/6dbac344-b91c-4d89-af03-bd11105039e9) ## Changelog - Added gridGap of 8px to Flex containing cancel and save buttons in `EditProtocolMetadataModal`. ## Review requests ## Risk assessment Co-authored-by: shiyaochen --- .../src/organisms/EditProtocolMetadataModal/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/protocol-designer/src/organisms/EditProtocolMetadataModal/index.tsx b/protocol-designer/src/organisms/EditProtocolMetadataModal/index.tsx index e62c919ec8a..8bd57308d56 100644 --- a/protocol-designer/src/organisms/EditProtocolMetadataModal/index.tsx +++ b/protocol-designer/src/organisms/EditProtocolMetadataModal/index.tsx @@ -60,6 +60,7 @@ export function EditProtocolMetadataModal( {t('shared:cancel')} From 4064bdff21a17cea8d1659c125f1d17548fbc1ee Mon Sep 17 00:00:00 2001 From: pmoegenburg Date: Tue, 17 Sep 2024 16:23:48 -0400 Subject: [PATCH 16/24] feat(api): add meniscus wellOrigin enum (#16139) # Overview This adds MENISCUS to the WellOrigin enums. This also adds methods that support using WellOrigin.MENISCUS. With this PR, Protocol Engine State GeometryView `get_well_position()` now accepts calls specifying WellOrigin.MENISCUS and pulls a well's liquid height (relative to well bottom) from Protocol Engine State WellView. Currently, a well's liquid height is populated only once a successful liquid probe has been done for that well. ## Test Plan and Hands on Testing ## Changelog ## Review requests ## Risk assessment --- .../protocol_api/core/engine/well.py | 11 +++ .../core/legacy/legacy_well_core.py | 4 + api/src/opentrons/protocol_api/core/well.py | 4 + api/src/opentrons/protocol_api/labware.py | 11 +++ .../protocol_engine/errors/__init__.py | 2 + .../protocol_engine/errors/exceptions.py | 13 ++++ .../protocol_engine/execution/pipetting.py | 4 +- .../protocol_engine/state/geometry.py | 13 ++++ .../opentrons/protocol_engine/state/state.py | 1 + .../opentrons/protocol_engine/state/wells.py | 2 +- api/src/opentrons/protocol_engine/types.py | 1 + .../protocols/api_support/definitions.py | 2 +- .../core/engine/test_well_core.py | 17 +++++ api/tests/opentrons/protocol_api/test_well.py | 11 +++ .../state/test_geometry_view.py | 75 +++++++++++++++++++ shared-data/command/schemas/9.json | 2 +- 16 files changed, 169 insertions(+), 4 deletions(-) diff --git a/api/src/opentrons/protocol_api/core/engine/well.py b/api/src/opentrons/protocol_api/core/engine/well.py index 6743a8a39c5..ec7307a6a90 100644 --- a/api/src/opentrons/protocol_api/core/engine/well.py +++ b/api/src/opentrons/protocol_api/core/engine/well.py @@ -125,6 +125,17 @@ def get_center(self) -> Point: well_location=WellLocation(origin=WellOrigin.CENTER), ) + def get_meniscus(self, z_offset: float) -> Point: + """Get the coordinate of the well's meniscus, with a z-offset.""" + return self._engine_client.state.geometry.get_well_position( + well_name=self._name, + labware_id=self._labware_id, + well_location=WellLocation( + origin=WellOrigin.MENISCUS, + offset=WellOffset(x=0, y=0, z=z_offset), + ), + ) + def load_liquid( self, liquid: Liquid, diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_well_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_well_core.py index a88dd2eee80..f37aefbd4be 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_well_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_well_core.py @@ -106,6 +106,10 @@ def get_center(self) -> Point: """Get the coordinate of the well's center.""" return self._geometry.center() + def get_meniscus(self, z_offset: float) -> Point: + """This will never be called because it was added in API 2.21.""" + assert False, "get_meniscus only supported in API 2.21 & later" + def load_liquid( self, liquid: Liquid, diff --git a/api/src/opentrons/protocol_api/core/well.py b/api/src/opentrons/protocol_api/core/well.py index bd58963a59c..81dddede2f1 100644 --- a/api/src/opentrons/protocol_api/core/well.py +++ b/api/src/opentrons/protocol_api/core/well.py @@ -71,6 +71,10 @@ def get_bottom(self, z_offset: float) -> Point: def get_center(self) -> Point: """Get the coordinate of the well's center.""" + @abstractmethod + def get_meniscus(self, z_offset: float) -> Point: + """Get the coordinate of the well's meniscus, with an z-offset.""" + @abstractmethod def load_liquid( self, diff --git a/api/src/opentrons/protocol_api/labware.py b/api/src/opentrons/protocol_api/labware.py index 480af5fd009..43c2c0ce5a8 100644 --- a/api/src/opentrons/protocol_api/labware.py +++ b/api/src/opentrons/protocol_api/labware.py @@ -221,6 +221,17 @@ def center(self) -> Location: """ return Location(self._core.get_center(), self) + @requires_version(2, 21) + def meniscus(self, z: float = 0.0) -> Location: + """ + :param z: An offset on the z-axis, in mm. Positive offsets are higher and + negative offsets are lower. + :return: A :py:class:`~opentrons.types.Location` corresponding to the + absolute position of the meniscus-center of the well, plus the ``z`` offset + (if specified). + """ + return Location(self._core.get_meniscus(z_offset=z), self) + @requires_version(2, 8) def from_center_cartesian(self, x: float, y: float, z: float) -> Point: """ diff --git a/api/src/opentrons/protocol_engine/errors/__init__.py b/api/src/opentrons/protocol_engine/errors/__init__.py index f5cd1728fb4..e0f60a5cd45 100644 --- a/api/src/opentrons/protocol_engine/errors/__init__.py +++ b/api/src/opentrons/protocol_engine/errors/__init__.py @@ -70,6 +70,7 @@ NotSupportedOnRobotType, CommandNotAllowedError, InvalidLiquidHeightFound, + LiquidHeightUnknownError, InvalidWellDefinitionError, ) @@ -149,5 +150,6 @@ "ErrorOccurrence", "CommandNotAllowedError", "InvalidLiquidHeightFound", + "LiquidHeightUnknownError", "InvalidWellDefinitionError", ] diff --git a/api/src/opentrons/protocol_engine/errors/exceptions.py b/api/src/opentrons/protocol_engine/errors/exceptions.py index 0b888372efc..57d420124a7 100644 --- a/api/src/opentrons/protocol_engine/errors/exceptions.py +++ b/api/src/opentrons/protocol_engine/errors/exceptions.py @@ -1015,6 +1015,19 @@ def __init__( super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping) +class LiquidHeightUnknownError(ProtocolEngineError): + """Raised when attempting to specify WellOrigin.MENISCUS before liquid probing has been done.""" + + def __init__( + self, + message: Optional[str] = None, + details: Optional[Dict[str, Any]] = None, + wrapping: Optional[Sequence[EnumeratedError]] = None, + ) -> None: + """Build a LiquidHeightUnknownError.""" + super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping) + + class EStopActivatedError(ProtocolEngineError): """Represents an E-stop event.""" diff --git a/api/src/opentrons/protocol_engine/execution/pipetting.py b/api/src/opentrons/protocol_engine/execution/pipetting.py index 96f8256ef25..fed6fc52ee6 100644 --- a/api/src/opentrons/protocol_engine/execution/pipetting.py +++ b/api/src/opentrons/protocol_engine/execution/pipetting.py @@ -194,7 +194,9 @@ async def liquid_probe_in_place( mount=hw_pipette.mount, max_z_dist=well_depth - lld_min_height + well_location.offset.z, ) - return float(z_pos) + labware_pos = self._state_view.geometry.get_labware_position(labware_id) + relative_height = z_pos - labware_pos.z - well_def.z + return float(relative_height) @contextmanager def _set_flow_rate( diff --git a/api/src/opentrons/protocol_engine/state/geometry.py b/api/src/opentrons/protocol_engine/state/geometry.py index 7a0871abddb..a0fef65e7ee 100644 --- a/api/src/opentrons/protocol_engine/state/geometry.py +++ b/api/src/opentrons/protocol_engine/state/geometry.py @@ -49,6 +49,7 @@ ) from .config import Config from .labware import LabwareView +from .wells import WellView from .modules import ModuleView from .pipettes import PipetteView from .addressable_areas import AddressableAreaView @@ -98,6 +99,7 @@ def __init__( self, config: Config, labware_view: LabwareView, + well_view: WellView, module_view: ModuleView, pipette_view: PipetteView, addressable_area_view: AddressableAreaView, @@ -105,6 +107,7 @@ def __init__( """Initialize a GeometryView instance.""" self._config = config self._labware = labware_view + self._wells = well_view self._modules = module_view self._pipettes = pipette_view self._addressable_areas = addressable_area_view @@ -430,6 +433,16 @@ def get_well_position( offset = offset.copy(update={"z": offset.z + well_depth}) elif well_location.origin == WellOrigin.CENTER: offset = offset.copy(update={"z": offset.z + well_depth / 2.0}) + elif well_location.origin == WellOrigin.MENISCUS: + liquid_height = self._wells.get_last_measured_liquid_height( + labware_id, well_name + ) + if liquid_height is not None: + offset = offset.copy(update={"z": offset.z + liquid_height}) + else: + raise errors.LiquidHeightUnknownError( + "Must liquid probe before specifying WellOrigin.MENISCUS." + ) return Point( x=labware_pos.x + offset.x + well_def.x, diff --git a/api/src/opentrons/protocol_engine/state/state.py b/api/src/opentrons/protocol_engine/state/state.py index 3bd7d087c2e..7fc23a8ee2f 100644 --- a/api/src/opentrons/protocol_engine/state/state.py +++ b/api/src/opentrons/protocol_engine/state/state.py @@ -354,6 +354,7 @@ def _initialize_state(self) -> None: self._geometry = GeometryView( config=self._config, labware_view=self._labware, + well_view=self._wells, module_view=self._modules, pipette_view=self._pipettes, addressable_area_view=self._addressable_areas, diff --git a/api/src/opentrons/protocol_engine/state/wells.py b/api/src/opentrons/protocol_engine/state/wells.py index e6e19446c6f..d74d94a1be0 100644 --- a/api/src/opentrons/protocol_engine/state/wells.py +++ b/api/src/opentrons/protocol_engine/state/wells.py @@ -52,7 +52,7 @@ def _handle_failed_command(self, action: FailCommandAction) -> None: self._set_liquid_height( labware_id=action.error.private.labware_id, well_name=action.error.private.well_name, - height=0, + height=None, time=action.failed_at, ) diff --git a/api/src/opentrons/protocol_engine/types.py b/api/src/opentrons/protocol_engine/types.py index 68b51d7c1b7..519d39b6ec7 100644 --- a/api/src/opentrons/protocol_engine/types.py +++ b/api/src/opentrons/protocol_engine/types.py @@ -210,6 +210,7 @@ class WellOrigin(str, Enum): TOP = "top" BOTTOM = "bottom" CENTER = "center" + MENISCUS = "meniscus" class DropTipWellOrigin(str, Enum): diff --git a/api/src/opentrons/protocols/api_support/definitions.py b/api/src/opentrons/protocols/api_support/definitions.py index 799af1993f3..ad692e03828 100644 --- a/api/src/opentrons/protocols/api_support/definitions.py +++ b/api/src/opentrons/protocols/api_support/definitions.py @@ -1,6 +1,6 @@ from .types import APIVersion -MAX_SUPPORTED_VERSION = APIVersion(2, 20) +MAX_SUPPORTED_VERSION = APIVersion(2, 21) """The maximum supported protocol API version in this release.""" MIN_SUPPORTED_VERSION = APIVersion(2, 0) diff --git a/api/tests/opentrons/protocol_api/core/engine/test_well_core.py b/api/tests/opentrons/protocol_api/core/engine/test_well_core.py index 31b562f7e81..96efbbdde8d 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_well_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_well_core.py @@ -149,6 +149,23 @@ def test_get_center( assert subject.get_center() == Point(1, 2, 3) +def test_get_meniscus( + decoy: Decoy, mock_engine_client: EngineClient, subject: WellCore +) -> None: + """It should get a well bottom.""" + decoy.when( + mock_engine_client.state.geometry.get_well_position( + labware_id="labware-id", + well_name="well-name", + well_location=WellLocation( + origin=WellOrigin.MENISCUS, offset=WellOffset(x=0, y=0, z=2.5) + ), + ) + ).then_return(Point(1, 2, 3)) + + assert subject.get_meniscus(z_offset=2.5) == Point(1, 2, 3) + + def test_has_tip( decoy: Decoy, mock_engine_client: EngineClient, subject: WellCore ) -> None: diff --git a/api/tests/opentrons/protocol_api/test_well.py b/api/tests/opentrons/protocol_api/test_well.py index 00cbbac8fa7..3a2ba81b9fa 100644 --- a/api/tests/opentrons/protocol_api/test_well.py +++ b/api/tests/opentrons/protocol_api/test_well.py @@ -101,6 +101,17 @@ def test_well_center(decoy: Decoy, mock_well_core: WellCore, subject: Well) -> N assert result.labware.as_well() is subject +def test_well_meniscus(decoy: Decoy, mock_well_core: WellCore, subject: Well) -> None: + """It should get a Location representing the meniscus of the well.""" + decoy.when(mock_well_core.get_meniscus(z_offset=4.2)).then_return(Point(1, 2, 3)) + + result = subject.meniscus(4.2) + + assert isinstance(result, Location) + assert result.point == Point(1, 2, 3) + assert result.labware.as_well() is subject + + def test_has_tip(decoy: Decoy, mock_well_core: WellCore, subject: Well) -> None: """It should get tip state from the core.""" decoy.when(mock_well_core.has_tip()).then_return(True) diff --git a/api/tests/opentrons/protocol_engine/state/test_geometry_view.py b/api/tests/opentrons/protocol_engine/state/test_geometry_view.py index 6e8b5632b06..1854d08523a 100644 --- a/api/tests/opentrons/protocol_engine/state/test_geometry_view.py +++ b/api/tests/opentrons/protocol_engine/state/test_geometry_view.py @@ -64,6 +64,7 @@ from opentrons.protocol_engine.state import _move_types from opentrons.protocol_engine.state.config import Config from opentrons.protocol_engine.state.labware import LabwareView, LabwareStore +from opentrons.protocol_engine.state.wells import WellView, WellStore from opentrons.protocol_engine.state.modules import ModuleView, ModuleStore from opentrons.protocol_engine.state.pipettes import ( PipetteView, @@ -94,6 +95,12 @@ def mock_labware_view(decoy: Decoy) -> LabwareView: return decoy.mock(cls=LabwareView) +@pytest.fixture +def mock_well_view(decoy: Decoy) -> WellView: + """Get a mock in the shape of a WellView.""" + return decoy.mock(cls=WellView) + + @pytest.fixture def mock_module_view(decoy: Decoy) -> ModuleView: """Get a mock in the shape of a ModuleView.""" @@ -152,6 +159,18 @@ def labware_view(labware_store: LabwareStore) -> LabwareView: return LabwareView(labware_store._state) +@pytest.fixture +def well_store() -> WellStore: + """Get a well store that can accept actions.""" + return WellStore() + + +@pytest.fixture +def well_view(well_store: WellStore) -> WellView: + """Get a well view of a real well store.""" + return WellView(well_store._state) + + @pytest.fixture def module_store(state_config: Config) -> ModuleStore: """Get a module store that can accept actions.""" @@ -242,11 +261,13 @@ def nice_adapter_definition() -> LabwareDefinition: @pytest.fixture def subject( mock_labware_view: LabwareView, + mock_well_view: WellView, mock_module_view: ModuleView, mock_pipette_view: PipetteView, mock_addressable_area_view: AddressableAreaView, state_config: Config, labware_view: LabwareView, + well_view: WellView, module_view: ModuleView, pipette_view: PipetteView, addressable_area_view: AddressableAreaView, @@ -267,6 +288,7 @@ def my_cool_test(subject: GeometryView) -> None: return GeometryView( config=state_config, labware_view=mock_labware_view if use_mocks else labware_view, + well_view=mock_well_view if use_mocks else well_view, module_view=mock_module_view if use_mocks else module_view, pipette_view=mock_pipette_view if use_mocks else pipette_view, addressable_area_view=mock_addressable_area_view @@ -1477,6 +1499,59 @@ def test_get_well_position_with_center_offset( ) +def test_get_well_position_with_meniscus_offset( + decoy: Decoy, + well_plate_def: LabwareDefinition, + mock_labware_view: LabwareView, + mock_well_view: WellView, + mock_addressable_area_view: AddressableAreaView, + subject: GeometryView, +) -> None: + """It should be able to get the position of a well center in a labware.""" + labware_data = LoadedLabware( + id="labware-id", + loadName="load-name", + definitionUri="definition-uri", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_4), + offsetId="offset-id", + ) + calibration_offset = LabwareOffsetVector(x=1, y=-2, z=3) + slot_pos = Point(4, 5, 6) + well_def = well_plate_def.wells["B2"] + + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_labware_view.get_labware_offset_vector("labware-id")).then_return( + calibration_offset + ) + decoy.when( + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) + ).then_return(slot_pos) + decoy.when(mock_labware_view.get_well_definition("labware-id", "B2")).then_return( + well_def + ) + decoy.when( + mock_well_view.get_last_measured_liquid_height("labware-id", "B2") + ).then_return(70.5) + + result = subject.get_well_position( + labware_id="labware-id", + well_name="B2", + well_location=WellLocation( + origin=WellOrigin.MENISCUS, + offset=WellOffset(x=2, y=3, z=4), + ), + ) + + assert result == Point( + x=slot_pos[0] + 1 + well_def.x + 2, + y=slot_pos[1] - 2 + well_def.y + 3, + z=slot_pos[2] + 3 + well_def.z + 4 + 70.5, + ) + + def test_get_relative_well_location( decoy: Decoy, well_plate_def: LabwareDefinition, diff --git a/shared-data/command/schemas/9.json b/shared-data/command/schemas/9.json index 4d63aee9fd5..263330eb6de 100644 --- a/shared-data/command/schemas/9.json +++ b/shared-data/command/schemas/9.json @@ -293,7 +293,7 @@ "WellOrigin": { "title": "WellOrigin", "description": "Origin of WellLocation offset.\n\nProps:\n TOP: the top-center of the well\n BOTTOM: the bottom-center of the well\n CENTER: the middle-center of the well", - "enum": ["top", "bottom", "center"], + "enum": ["top", "bottom", "center", "meniscus"], "type": "string" }, "WellOffset": { From 84e5a8dfb55bfdc321a42f1d742e67e7936cba02 Mon Sep 17 00:00:00 2001 From: Seth Foster Date: Tue, 17 Sep 2024 16:55:49 -0400 Subject: [PATCH 17/24] refactor(app): Move robot settings dashboard to ODD (#16271) Another in a string of location refactors - the RobotSettingsDashboard and NetworkSettings organisms are ODD-only and can go in the ODD subdirectory. We can also pull out the `useIsOnboardingFlowOngoing` hook to a new `organisms/ODD/hooks` directory. --- .../organisms/EmergencyStop/EstopTakeover.tsx | 2 +- .../__tests__/EstopTakeover.test.tsx | 4 +-- .../FirmwareUpdateTakeover.tsx | 2 +- .../__tests__/FirmwareUpdateTakeover.test.tsx | 4 +-- .../AlternativeSecurityTypeModal.tsx | 6 ++-- .../NetworkSettings/ConnectingNetwork.tsx | 0 .../NetworkSettings/DisplaySearchNetwork.tsx | 0 .../NetworkSettings/DisplayWifiList.tsx | 6 ++-- .../NetworkSettings/FailedToConnect.tsx | 4 +-- .../SelectAuthenticationType.tsx | 8 ++--- .../{ => ODD}/NetworkSettings/SetWifiCred.tsx | 4 +-- .../{ => ODD}/NetworkSettings/SetWifiSsid.tsx | 4 +-- .../NetworkSettings/WifiConnectionDetails.tsx | 10 +++---- .../AlternativeSecurityTypeModal.test.tsx | 4 +-- .../__tests__/ConnectingNetwork.test.tsx | 4 +-- .../__tests__/DisplaySearchNetwork.test.tsx | 4 +-- .../__tests__/DisplayWifiList.test.tsx | 10 +++---- .../__tests__/FailedToConnect.test.tsx | 6 ++-- .../SelectAuthenticationType.test.tsx | 17 ++++++----- .../__tests__/SetWifiCred.test.tsx | 8 ++--- .../__tests__/SetWifiSsid.test.tsx | 4 +-- .../__tests__/WifiConnectionDetails.test.tsx | 19 +++++++----- .../{ => ODD}/NetworkSettings/index.ts | 0 .../RobotSettingsDashboard/DeviceReset.tsx | 18 +++++------ .../EthernetConnectionDetails.tsx | 8 ++--- .../NetworkSettings/NetworkDetailsModal.tsx | 4 +-- .../RobotSettingsJoinOtherNetwork.tsx | 6 ++-- .../RobotSettingsSelectAuthenticationType.tsx | 6 ++-- .../RobotSettingsSetWifiCred.tsx | 6 ++-- .../NetworkSettings/RobotSettingsWifi.tsx | 4 +-- .../RobotSettingsWifiConnect.tsx | 13 ++++---- .../NetworkSettings/WifiConnectionDetails.tsx | 8 ++--- .../EthernetConnectionDetails.test.tsx | 18 +++++------ .../__tests__/NetworkDetailsModal.test.tsx | 4 +-- .../__tests__/NetworkSettings.test.tsx | 16 +++++----- .../__tests__/WifiConnectionDetails.test.tsx | 14 ++++----- .../NetworkSettings/index.tsx | 6 ++-- .../RobotSettingsDashboard/OnOffToggle.tsx | 30 +++++++++++++++++++ .../RobotSettingsDashboard/Privacy.tsx | 12 ++++---- .../RobotSettingsDashboard/RobotName.tsx | 2 +- .../RobotSettingButton.tsx | 0 .../RobotSystemVersion.tsx | 8 ++--- .../RobotSystemVersionModal.tsx | 10 +++---- .../RobotSettingsDashboard/TextSize.tsx | 2 +- .../TouchScreenSleep.tsx | 10 +++---- .../TouchscreenBrightness.tsx | 10 +++---- .../RobotSettingsDashboard/UpdateChannel.tsx | 6 ++-- .../__tests__/DeviceReset.test.tsx | 17 ++++++----- .../__tests__/Privacy.test.tsx | 12 ++++---- .../__tests__/RobotSystemVersion.test.tsx | 6 ++-- .../RobotSystemVersionModal.test.tsx | 4 +-- .../__tests__/TextSize.test.tsx | 4 +-- .../__tests__/TouchScreenSleep.test.tsx | 8 ++--- .../__tests__/TouchscreenBrightness.test.tsx | 8 ++--- .../__tests__/UpdateChannel.test.tsx | 8 ++--- .../{ => ODD}/RobotSettingsDashboard/index.ts | 3 ++ .../ODD/RobotSettingsDashboard/types.ts | 21 +++++++++++++ .../useIsUnboxingFlowOngoing.test.tsx} | 2 +- app/src/organisms/ODD/hooks/index.ts | 1 + .../hooks/useIsUnboxingFlowOngoing.ts} | 0 .../pages/ODD/ConnectViaEthernet/index.tsx | 2 +- .../ODD/ConnectViaWifi/JoinOtherNetwork.tsx | 2 +- .../SelectAuthenticationType.tsx | 2 +- .../pages/ODD/ConnectViaWifi/SetWifiCred.tsx | 2 +- .../ODD/ConnectViaWifi/WifiConnectStatus.tsx | 2 +- app/src/pages/ODD/ConnectViaWifi/index.tsx | 2 +- .../NameRobot/__tests__/NameRobot.test.tsx | 4 +-- app/src/pages/ODD/NameRobot/index.tsx | 2 +- .../RobotSettingsList.tsx | 25 ++++------------ .../__tests__/RobotSettingsDashboard.test.tsx | 18 ++++++----- .../ODD/RobotSettingsDashboard/index.tsx | 27 ++--------------- 71 files changed, 280 insertions(+), 253 deletions(-) rename app/src/organisms/{ => ODD}/NetworkSettings/AlternativeSecurityTypeModal.tsx (90%) rename app/src/organisms/{ => ODD}/NetworkSettings/ConnectingNetwork.tsx (100%) rename app/src/organisms/{ => ODD}/NetworkSettings/DisplaySearchNetwork.tsx (100%) rename app/src/organisms/{ => ODD}/NetworkSettings/DisplayWifiList.tsx (93%) rename app/src/organisms/{ => ODD}/NetworkSettings/FailedToConnect.tsx (95%) rename app/src/organisms/{ => ODD}/NetworkSettings/SelectAuthenticationType.tsx (93%) rename app/src/organisms/{ => ODD}/NetworkSettings/SetWifiCred.tsx (95%) rename app/src/organisms/{ => ODD}/NetworkSettings/SetWifiSsid.tsx (91%) rename app/src/organisms/{ => ODD}/NetworkSettings/WifiConnectionDetails.tsx (92%) rename app/src/organisms/{ => ODD}/NetworkSettings/__tests__/AlternativeSecurityTypeModal.test.tsx (94%) rename app/src/organisms/{ => ODD}/NetworkSettings/__tests__/ConnectingNetwork.test.tsx (89%) rename app/src/organisms/{ => ODD}/NetworkSettings/__tests__/DisplaySearchNetwork.test.tsx (85%) rename app/src/organisms/{ => ODD}/NetworkSettings/__tests__/DisplayWifiList.test.tsx (89%) rename app/src/organisms/{ => ODD}/NetworkSettings/__tests__/FailedToConnect.test.tsx (91%) rename app/src/organisms/{ => ODD}/NetworkSettings/__tests__/SelectAuthenticationType.test.tsx (85%) rename app/src/organisms/{ => ODD}/NetworkSettings/__tests__/SetWifiCred.test.tsx (90%) rename app/src/organisms/{ => ODD}/NetworkSettings/__tests__/SetWifiSsid.test.tsx (93%) rename app/src/organisms/{ => ODD}/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx (84%) rename app/src/organisms/{ => ODD}/NetworkSettings/index.ts (100%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/DeviceReset.tsx (94%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/NetworkSettings/EthernetConnectionDetails.tsx (93%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal.tsx (94%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/NetworkSettings/RobotSettingsJoinOtherNetwork.tsx (86%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/NetworkSettings/RobotSettingsSelectAuthenticationType.tsx (84%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/NetworkSettings/RobotSettingsSetWifiCred.tsx (81%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifi.tsx (90%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifiConnect.tsx (84%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/NetworkSettings/WifiConnectionDetails.tsx (95%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/NetworkSettings/__tests__/EthernetConnectionDetails.test.tsx (81%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkDetailsModal.test.tsx (95%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkSettings.test.tsx (85%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx (87%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/NetworkSettings/index.tsx (95%) create mode 100644 app/src/organisms/ODD/RobotSettingsDashboard/OnOffToggle.tsx rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/Privacy.tsx (80%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/RobotName.tsx (91%) rename app/src/{pages => organisms}/ODD/RobotSettingsDashboard/RobotSettingButton.tsx (100%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/RobotSystemVersion.tsx (91%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/RobotSystemVersionModal.tsx (84%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/TextSize.tsx (97%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/TouchScreenSleep.tsx (89%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/TouchscreenBrightness.tsx (91%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/UpdateChannel.tsx (96%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/__tests__/DeviceReset.test.tsx (93%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/__tests__/Privacy.test.tsx (78%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/__tests__/RobotSystemVersion.test.tsx (93%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/__tests__/RobotSystemVersionModal.test.tsx (94%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/__tests__/TextSize.test.tsx (91%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/__tests__/TouchScreenSleep.test.tsx (86%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/__tests__/TouchscreenBrightness.test.tsx (92%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/__tests__/UpdateChannel.test.tsx (92%) rename app/src/organisms/{ => ODD}/RobotSettingsDashboard/index.ts (86%) create mode 100644 app/src/organisms/ODD/RobotSettingsDashboard/types.ts rename app/src/organisms/{RobotSettingsDashboard/NetworkSettings/__tests__/hooks.test.tsx => ODD/hooks/__tests__/useIsUnboxingFlowOngoing.test.tsx} (96%) create mode 100644 app/src/organisms/ODD/hooks/index.ts rename app/src/organisms/{RobotSettingsDashboard/NetworkSettings/hooks.ts => ODD/hooks/useIsUnboxingFlowOngoing.ts} (100%) diff --git a/app/src/organisms/EmergencyStop/EstopTakeover.tsx b/app/src/organisms/EmergencyStop/EstopTakeover.tsx index c8e294e2bfa..4faf63023c5 100644 --- a/app/src/organisms/EmergencyStop/EstopTakeover.tsx +++ b/app/src/organisms/EmergencyStop/EstopTakeover.tsx @@ -5,7 +5,7 @@ import { useEstopQuery } from '@opentrons/react-api-client' import { EstopPressedModal } from './EstopPressedModal' import { EstopMissingModal } from './EstopMissingModal' import { useEstopContext } from './hooks' -import { useIsUnboxingFlowOngoing } from '../RobotSettingsDashboard/NetworkSettings/hooks' +import { useIsUnboxingFlowOngoing } from '../ODD/hooks' import { getLocalRobot } from '../../redux/discovery' import { PHYSICALLY_ENGAGED, diff --git a/app/src/organisms/EmergencyStop/__tests__/EstopTakeover.test.tsx b/app/src/organisms/EmergencyStop/__tests__/EstopTakeover.test.tsx index 7e31c8fa54f..f169685ab65 100644 --- a/app/src/organisms/EmergencyStop/__tests__/EstopTakeover.test.tsx +++ b/app/src/organisms/EmergencyStop/__tests__/EstopTakeover.test.tsx @@ -8,7 +8,7 @@ import { useEstopQuery } from '@opentrons/react-api-client' import { i18n } from '../../../i18n' import { EstopMissingModal } from '../EstopMissingModal' import { EstopPressedModal } from '../EstopPressedModal' -import { useIsUnboxingFlowOngoing } from '../../RobotSettingsDashboard/NetworkSettings/hooks' +import { useIsUnboxingFlowOngoing } from '../../ODD/hooks' import { ENGAGED, LOGICALLY_ENGAGED, @@ -22,7 +22,7 @@ import { EstopTakeover } from '../EstopTakeover' vi.mock('@opentrons/react-api-client') vi.mock('../EstopMissingModal') vi.mock('../EstopPressedModal') -vi.mock('../../RobotSettingsDashboard/NetworkSettings/hooks') +vi.mock('../../ODD/hooks') vi.mock('../../../redux/discovery') const mockPressed = { diff --git a/app/src/organisms/FirmwareUpdateModal/FirmwareUpdateTakeover.tsx b/app/src/organisms/FirmwareUpdateModal/FirmwareUpdateTakeover.tsx index 33d581ea5e4..b47454a03d7 100644 --- a/app/src/organisms/FirmwareUpdateModal/FirmwareUpdateTakeover.tsx +++ b/app/src/organisms/FirmwareUpdateModal/FirmwareUpdateTakeover.tsx @@ -8,7 +8,7 @@ import { } from '@opentrons/react-api-client' import { useNotifyCurrentMaintenanceRun } from '../../resources/maintenance_runs' import { getTopPortalEl } from '../../App/portal' -import { useIsUnboxingFlowOngoing } from '../RobotSettingsDashboard/NetworkSettings/hooks' +import { useIsUnboxingFlowOngoing } from '../ODD/hooks' import { UpdateInProgressModal } from './UpdateInProgressModal' import { UpdateNeededModal } from './UpdateNeededModal' import type { Subsystem, InstrumentData } from '@opentrons/api-client' diff --git a/app/src/organisms/FirmwareUpdateModal/__tests__/FirmwareUpdateTakeover.test.tsx b/app/src/organisms/FirmwareUpdateModal/__tests__/FirmwareUpdateTakeover.test.tsx index 3816b85261f..2cc4c53e5c9 100644 --- a/app/src/organisms/FirmwareUpdateModal/__tests__/FirmwareUpdateTakeover.test.tsx +++ b/app/src/organisms/FirmwareUpdateModal/__tests__/FirmwareUpdateTakeover.test.tsx @@ -12,7 +12,7 @@ import { import { i18n } from '../../../i18n' import { UpdateNeededModal } from '../UpdateNeededModal' import { UpdateInProgressModal } from '../UpdateInProgressModal' -import { useIsUnboxingFlowOngoing } from '../../RobotSettingsDashboard/NetworkSettings/hooks' +import { useIsUnboxingFlowOngoing } from '../../ODD/hooks' import { FirmwareUpdateTakeover } from '../FirmwareUpdateTakeover' import { useNotifyCurrentMaintenanceRun } from '../../../resources/maintenance_runs' @@ -21,7 +21,7 @@ import type { BadPipette, PipetteData } from '@opentrons/api-client' vi.mock('@opentrons/react-api-client') vi.mock('../UpdateNeededModal') vi.mock('../UpdateInProgressModal') -vi.mock('../../RobotSettingsDashboard/NetworkSettings/hooks') +vi.mock('../../ODD/hooks') vi.mock('../../../resources/maintenance_runs') const render = () => { diff --git a/app/src/organisms/NetworkSettings/AlternativeSecurityTypeModal.tsx b/app/src/organisms/ODD/NetworkSettings/AlternativeSecurityTypeModal.tsx similarity index 90% rename from app/src/organisms/NetworkSettings/AlternativeSecurityTypeModal.tsx rename to app/src/organisms/ODD/NetworkSettings/AlternativeSecurityTypeModal.tsx index 8709d9bdca5..3d85d2dddc4 100644 --- a/app/src/organisms/NetworkSettings/AlternativeSecurityTypeModal.tsx +++ b/app/src/organisms/ODD/NetworkSettings/AlternativeSecurityTypeModal.tsx @@ -12,10 +12,10 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { SmallButton } from '../../atoms/buttons' -import { OddModal } from '../../molecules/OddModal' +import { SmallButton } from '../../../atoms/buttons' +import { OddModal } from '../../../molecules/OddModal' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '../../../molecules/OddModal/types' interface AlternativeSecurityTypeModalProps { setShowAlternativeSecurityTypeModal: ( diff --git a/app/src/organisms/NetworkSettings/ConnectingNetwork.tsx b/app/src/organisms/ODD/NetworkSettings/ConnectingNetwork.tsx similarity index 100% rename from app/src/organisms/NetworkSettings/ConnectingNetwork.tsx rename to app/src/organisms/ODD/NetworkSettings/ConnectingNetwork.tsx diff --git a/app/src/organisms/NetworkSettings/DisplaySearchNetwork.tsx b/app/src/organisms/ODD/NetworkSettings/DisplaySearchNetwork.tsx similarity index 100% rename from app/src/organisms/NetworkSettings/DisplaySearchNetwork.tsx rename to app/src/organisms/ODD/NetworkSettings/DisplaySearchNetwork.tsx diff --git a/app/src/organisms/NetworkSettings/DisplayWifiList.tsx b/app/src/organisms/ODD/NetworkSettings/DisplayWifiList.tsx similarity index 93% rename from app/src/organisms/NetworkSettings/DisplayWifiList.tsx rename to app/src/organisms/ODD/NetworkSettings/DisplayWifiList.tsx index 2925a47f392..476db7169d5 100644 --- a/app/src/organisms/NetworkSettings/DisplayWifiList.tsx +++ b/app/src/organisms/ODD/NetworkSettings/DisplayWifiList.tsx @@ -17,11 +17,11 @@ import { LegacyStyledText, } from '@opentrons/components' -import { ODD_FOCUS_VISIBLE } from '../../atoms/buttons/constants' -import { RobotSetupHeader } from '../../organisms/RobotSetupHeader' +import { ODD_FOCUS_VISIBLE } from '../../../atoms/buttons/constants' +import { RobotSetupHeader } from '../../../organisms/RobotSetupHeader' import { DisplaySearchNetwork } from './DisplaySearchNetwork' -import type { WifiNetwork } from '../../redux/networking/types' +import type { WifiNetwork } from '../../../redux/networking/types' const NETWORK_ROW_STYLE = css` display: ${DISPLAY_FLEX}; diff --git a/app/src/organisms/NetworkSettings/FailedToConnect.tsx b/app/src/organisms/ODD/NetworkSettings/FailedToConnect.tsx similarity index 95% rename from app/src/organisms/NetworkSettings/FailedToConnect.tsx rename to app/src/organisms/ODD/NetworkSettings/FailedToConnect.tsx index aa334813352..2cfdd9092d8 100644 --- a/app/src/organisms/NetworkSettings/FailedToConnect.tsx +++ b/app/src/organisms/ODD/NetworkSettings/FailedToConnect.tsx @@ -14,9 +14,9 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { MediumButton } from '../../atoms/buttons' +import { MediumButton } from '../../../atoms/buttons' -import type { RequestState } from '../../redux/robot-api/types' +import type { RequestState } from '../../../redux/robot-api/types' interface FailedToConnectProps { selectedSsid: string diff --git a/app/src/organisms/NetworkSettings/SelectAuthenticationType.tsx b/app/src/organisms/ODD/NetworkSettings/SelectAuthenticationType.tsx similarity index 93% rename from app/src/organisms/NetworkSettings/SelectAuthenticationType.tsx rename to app/src/organisms/ODD/NetworkSettings/SelectAuthenticationType.tsx index 39906414758..455c91d432f 100644 --- a/app/src/organisms/NetworkSettings/SelectAuthenticationType.tsx +++ b/app/src/organisms/ODD/NetworkSettings/SelectAuthenticationType.tsx @@ -16,13 +16,13 @@ import { RadioButton, } from '@opentrons/components' -import { getLocalRobot } from '../../redux/discovery' -import { getNetworkInterfaces, fetchStatus } from '../../redux/networking' -import { useIsUnboxingFlowOngoing } from '../RobotSettingsDashboard/NetworkSettings/hooks' +import { getLocalRobot } from '../../../redux/discovery' +import { getNetworkInterfaces, fetchStatus } from '../../../redux/networking' +import { useIsUnboxingFlowOngoing } from '../hooks' import { AlternativeSecurityTypeModal } from './AlternativeSecurityTypeModal' import type { WifiSecurityType } from '@opentrons/api-client' -import type { Dispatch, State } from '../../redux/types' +import type { Dispatch, State } from '../../../redux/types' interface SelectAuthenticationTypeProps { selectedAuthType: WifiSecurityType diff --git a/app/src/organisms/NetworkSettings/SetWifiCred.tsx b/app/src/organisms/ODD/NetworkSettings/SetWifiCred.tsx similarity index 95% rename from app/src/organisms/NetworkSettings/SetWifiCred.tsx rename to app/src/organisms/ODD/NetworkSettings/SetWifiCred.tsx index aa0bc30f717..7d1af2ab2e8 100644 --- a/app/src/organisms/NetworkSettings/SetWifiCred.tsx +++ b/app/src/organisms/ODD/NetworkSettings/SetWifiCred.tsx @@ -18,8 +18,8 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { FullKeyboard } from '../../atoms/SoftwareKeyboard' -import { useIsUnboxingFlowOngoing } from '../RobotSettingsDashboard/NetworkSettings/hooks' +import { FullKeyboard } from '../../../atoms/SoftwareKeyboard' +import { useIsUnboxingFlowOngoing } from '../hooks' interface SetWifiCredProps { password: string diff --git a/app/src/organisms/NetworkSettings/SetWifiSsid.tsx b/app/src/organisms/ODD/NetworkSettings/SetWifiSsid.tsx similarity index 91% rename from app/src/organisms/NetworkSettings/SetWifiSsid.tsx rename to app/src/organisms/ODD/NetworkSettings/SetWifiSsid.tsx index 7f987144cb4..05c0b7a13a2 100644 --- a/app/src/organisms/NetworkSettings/SetWifiSsid.tsx +++ b/app/src/organisms/ODD/NetworkSettings/SetWifiSsid.tsx @@ -12,8 +12,8 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { FullKeyboard } from '../../atoms/SoftwareKeyboard' -import { useIsUnboxingFlowOngoing } from '../RobotSettingsDashboard/NetworkSettings/hooks' +import { FullKeyboard } from '../../../atoms/SoftwareKeyboard' +import { useIsUnboxingFlowOngoing } from '../hooks' interface SetWifiSsidProps { errorMessage?: string | null diff --git a/app/src/organisms/NetworkSettings/WifiConnectionDetails.tsx b/app/src/organisms/ODD/NetworkSettings/WifiConnectionDetails.tsx similarity index 92% rename from app/src/organisms/NetworkSettings/WifiConnectionDetails.tsx rename to app/src/organisms/ODD/NetworkSettings/WifiConnectionDetails.tsx index ec95615c4e5..e3cb9e18ba6 100644 --- a/app/src/organisms/NetworkSettings/WifiConnectionDetails.tsx +++ b/app/src/organisms/ODD/NetworkSettings/WifiConnectionDetails.tsx @@ -17,14 +17,14 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { MediumButton } from '../../atoms/buttons' -import { RobotSetupHeader } from '../../organisms/RobotSetupHeader' -import { getLocalRobot } from '../../redux/discovery' -import { getNetworkInterfaces, fetchStatus } from '../../redux/networking' +import { MediumButton } from '../../../atoms/buttons' +import { RobotSetupHeader } from '../../../organisms/RobotSetupHeader' +import { getLocalRobot } from '../../../redux/discovery' +import { getNetworkInterfaces, fetchStatus } from '../../../redux/networking' import { NetworkDetailsModal } from '../RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal' import type { WifiSecurityType } from '@opentrons/api-client' -import type { Dispatch, State } from '../../redux/types' +import type { Dispatch, State } from '../../../redux/types' interface WifiConnectionDetailsProps { ssid?: string diff --git a/app/src/organisms/NetworkSettings/__tests__/AlternativeSecurityTypeModal.test.tsx b/app/src/organisms/ODD/NetworkSettings/__tests__/AlternativeSecurityTypeModal.test.tsx similarity index 94% rename from app/src/organisms/NetworkSettings/__tests__/AlternativeSecurityTypeModal.test.tsx rename to app/src/organisms/ODD/NetworkSettings/__tests__/AlternativeSecurityTypeModal.test.tsx index ee23afbee84..4c091f8e0ae 100644 --- a/app/src/organisms/NetworkSettings/__tests__/AlternativeSecurityTypeModal.test.tsx +++ b/app/src/organisms/ODD/NetworkSettings/__tests__/AlternativeSecurityTypeModal.test.tsx @@ -2,8 +2,8 @@ import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '../../../../__testing-utils__' +import { i18n } from '../../../../i18n' import { AlternativeSecurityTypeModal } from '../AlternativeSecurityTypeModal' import type { NavigateFunction } from 'react-router-dom' diff --git a/app/src/organisms/NetworkSettings/__tests__/ConnectingNetwork.test.tsx b/app/src/organisms/ODD/NetworkSettings/__tests__/ConnectingNetwork.test.tsx similarity index 89% rename from app/src/organisms/NetworkSettings/__tests__/ConnectingNetwork.test.tsx rename to app/src/organisms/ODD/NetworkSettings/__tests__/ConnectingNetwork.test.tsx index 9d19cba1822..32bcd2e81cb 100644 --- a/app/src/organisms/NetworkSettings/__tests__/ConnectingNetwork.test.tsx +++ b/app/src/organisms/ODD/NetworkSettings/__tests__/ConnectingNetwork.test.tsx @@ -3,8 +3,8 @@ import { MemoryRouter } from 'react-router-dom' import { screen } from '@testing-library/react' import { beforeEach, describe, expect, it } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '../../../../__testing-utils__' +import { i18n } from '../../../../i18n' import { ConnectingNetwork } from '../ConnectingNetwork' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/NetworkSettings/__tests__/DisplaySearchNetwork.test.tsx b/app/src/organisms/ODD/NetworkSettings/__tests__/DisplaySearchNetwork.test.tsx similarity index 85% rename from app/src/organisms/NetworkSettings/__tests__/DisplaySearchNetwork.test.tsx rename to app/src/organisms/ODD/NetworkSettings/__tests__/DisplaySearchNetwork.test.tsx index e582356a474..e82aedb63ca 100644 --- a/app/src/organisms/NetworkSettings/__tests__/DisplaySearchNetwork.test.tsx +++ b/app/src/organisms/ODD/NetworkSettings/__tests__/DisplaySearchNetwork.test.tsx @@ -4,8 +4,8 @@ import { describe, expect, it } from 'vitest' import { COLORS } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '../../../../__testing-utils__' +import { i18n } from '../../../../i18n' import { DisplaySearchNetwork } from '../DisplaySearchNetwork' const render = () => { diff --git a/app/src/organisms/NetworkSettings/__tests__/DisplayWifiList.test.tsx b/app/src/organisms/ODD/NetworkSettings/__tests__/DisplayWifiList.test.tsx similarity index 89% rename from app/src/organisms/NetworkSettings/__tests__/DisplayWifiList.test.tsx rename to app/src/organisms/ODD/NetworkSettings/__tests__/DisplayWifiList.test.tsx index 2a901ee1850..1970dc85242 100644 --- a/app/src/organisms/NetworkSettings/__tests__/DisplayWifiList.test.tsx +++ b/app/src/organisms/ODD/NetworkSettings/__tests__/DisplayWifiList.test.tsx @@ -2,9 +2,9 @@ import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import * as Fixtures from '../../../redux/networking/__fixtures__' +import { renderWithProviders } from '../../../../__testing-utils__' +import { i18n } from '../../../../i18n' +import * as Fixtures from '../../../../redux/networking/__fixtures__' import { DisplaySearchNetwork } from '../DisplaySearchNetwork' import { DisplayWifiList } from '../DisplayWifiList' @@ -20,8 +20,8 @@ const mockWifiList = [ }, ] -vi.mock('../../../redux/networking/selectors') -vi.mock('../../../redux/discovery/selectors') +vi.mock('../../../../redux/networking/selectors') +vi.mock('../../../../redux/discovery/selectors') vi.mock('../DisplaySearchNetwork') vi.mock('react-router-dom', async importOriginal => { const actual = await importOriginal() diff --git a/app/src/organisms/NetworkSettings/__tests__/FailedToConnect.test.tsx b/app/src/organisms/ODD/NetworkSettings/__tests__/FailedToConnect.test.tsx similarity index 91% rename from app/src/organisms/NetworkSettings/__tests__/FailedToConnect.test.tsx rename to app/src/organisms/ODD/NetworkSettings/__tests__/FailedToConnect.test.tsx index 22a9a4d9440..894eac79887 100644 --- a/app/src/organisms/NetworkSettings/__tests__/FailedToConnect.test.tsx +++ b/app/src/organisms/ODD/NetworkSettings/__tests__/FailedToConnect.test.tsx @@ -3,11 +3,11 @@ import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '../../../../__testing-utils__' +import { i18n } from '../../../../i18n' import { FailedToConnect } from '../FailedToConnect' -import type { RequestState } from '../../../redux/robot-api/types' +import type { RequestState } from '../../../../redux/robot-api/types' const render = (props: React.ComponentProps) => { return renderWithProviders( diff --git a/app/src/organisms/NetworkSettings/__tests__/SelectAuthenticationType.test.tsx b/app/src/organisms/ODD/NetworkSettings/__tests__/SelectAuthenticationType.test.tsx similarity index 85% rename from app/src/organisms/NetworkSettings/__tests__/SelectAuthenticationType.test.tsx rename to app/src/organisms/ODD/NetworkSettings/__tests__/SelectAuthenticationType.test.tsx index d014f2b5316..00b53dfd25f 100644 --- a/app/src/organisms/NetworkSettings/__tests__/SelectAuthenticationType.test.tsx +++ b/app/src/organisms/ODD/NetworkSettings/__tests__/SelectAuthenticationType.test.tsx @@ -3,10 +3,13 @@ import { fireEvent, screen } from '@testing-library/react' import { MemoryRouter } from 'react-router-dom' import { afterEach, beforeEach, describe, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { getNetworkInterfaces, INTERFACE_WIFI } from '../../../redux/networking' -import { useIsUnboxingFlowOngoing } from '../../RobotSettingsDashboard/NetworkSettings/hooks' +import { renderWithProviders } from '../../../../__testing-utils__' +import { i18n } from '../../../../i18n' +import { + getNetworkInterfaces, + INTERFACE_WIFI, +} from '../../../../redux/networking' +import { useIsUnboxingFlowOngoing } from '../../hooks' import { AlternativeSecurityTypeModal } from '../AlternativeSecurityTypeModal' import { SelectAuthenticationType } from '../SelectAuthenticationType' import { SetWifiCred } from '../SetWifiCred' @@ -17,10 +20,10 @@ const mockNavigate = vi.fn() const mockSetSelectedAuthType = vi.fn() vi.mock('../SetWifiCred') -vi.mock('../../../redux/networking') -vi.mock('../../../redux/discovery/selectors') +vi.mock('../../../../redux/networking') +vi.mock('../../../../redux/discovery/selectors') vi.mock('../AlternativeSecurityTypeModal') -vi.mock('../../RobotSettingsDashboard/NetworkSettings/hooks') +vi.mock('../../hooks') vi.mock('react-router-dom', async importOriginal => { const actual = await importOriginal() return { diff --git a/app/src/organisms/NetworkSettings/__tests__/SetWifiCred.test.tsx b/app/src/organisms/ODD/NetworkSettings/__tests__/SetWifiCred.test.tsx similarity index 90% rename from app/src/organisms/NetworkSettings/__tests__/SetWifiCred.test.tsx rename to app/src/organisms/ODD/NetworkSettings/__tests__/SetWifiCred.test.tsx index 51444bea730..9c2d6867ab8 100644 --- a/app/src/organisms/NetworkSettings/__tests__/SetWifiCred.test.tsx +++ b/app/src/organisms/ODD/NetworkSettings/__tests__/SetWifiCred.test.tsx @@ -3,13 +3,13 @@ import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '../../../../__testing-utils__' +import { i18n } from '../../../../i18n' import { SetWifiCred } from '../SetWifiCred' const mockSetPassword = vi.fn() -vi.mock('../../../redux/discovery') -vi.mock('../../../redux/robot-api') +vi.mock('../../../../redux/discovery') +vi.mock('../../../../redux/robot-api') const render = (props: React.ComponentProps) => { return renderWithProviders( diff --git a/app/src/organisms/NetworkSettings/__tests__/SetWifiSsid.test.tsx b/app/src/organisms/ODD/NetworkSettings/__tests__/SetWifiSsid.test.tsx similarity index 93% rename from app/src/organisms/NetworkSettings/__tests__/SetWifiSsid.test.tsx rename to app/src/organisms/ODD/NetworkSettings/__tests__/SetWifiSsid.test.tsx index 761364da978..017155a8fe2 100644 --- a/app/src/organisms/NetworkSettings/__tests__/SetWifiSsid.test.tsx +++ b/app/src/organisms/ODD/NetworkSettings/__tests__/SetWifiSsid.test.tsx @@ -3,8 +3,8 @@ import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '../../../../__testing-utils__' +import { i18n } from '../../../../i18n' import { SetWifiSsid } from '../SetWifiSsid' const mockSetSelectedSsid = vi.fn() diff --git a/app/src/organisms/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx b/app/src/organisms/ODD/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx similarity index 84% rename from app/src/organisms/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx rename to app/src/organisms/ODD/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx index 3c5427d3426..e1426610ebf 100644 --- a/app/src/organisms/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx +++ b/app/src/organisms/ODD/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx @@ -3,19 +3,22 @@ import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { useWifiList } from '../../../resources/networking/hooks' -import { getNetworkInterfaces, INTERFACE_WIFI } from '../../../redux/networking' -import * as Fixtures from '../../../redux/networking/__fixtures__' +import { renderWithProviders } from '../../../../__testing-utils__' +import { i18n } from '../../../../i18n' +import { useWifiList } from '../../../../resources/networking/hooks' +import { + getNetworkInterfaces, + INTERFACE_WIFI, +} from '../../../../redux/networking' +import * as Fixtures from '../../../../redux/networking/__fixtures__' import { NetworkDetailsModal } from '../../RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal' import { WifiConnectionDetails } from '../WifiConnectionDetails' import type { NavigateFunction } from 'react-router-dom' -vi.mock('../../../resources/networking/hooks') -vi.mock('../../../redux/networking') -vi.mock('../../../redux/discovery/selectors') +vi.mock('../../../../resources/networking/hooks') +vi.mock('../../../../redux/networking') +vi.mock('../../../../redux/discovery/selectors') vi.mock('../../RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal') const mockNavigate = vi.fn() diff --git a/app/src/organisms/NetworkSettings/index.ts b/app/src/organisms/ODD/NetworkSettings/index.ts similarity index 100% rename from app/src/organisms/NetworkSettings/index.ts rename to app/src/organisms/ODD/NetworkSettings/index.ts diff --git a/app/src/organisms/RobotSettingsDashboard/DeviceReset.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/DeviceReset.tsx similarity index 94% rename from app/src/organisms/RobotSettingsDashboard/DeviceReset.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/DeviceReset.tsx index 17ed18f2fd5..0153e40f22d 100644 --- a/app/src/organisms/RobotSettingsDashboard/DeviceReset.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/DeviceReset.tsx @@ -15,20 +15,20 @@ import { useConditionalConfirm, } from '@opentrons/components' -import { MediumButton, SmallButton } from '../../atoms/buttons' -import { OddModal } from '../../molecules/OddModal' -import { ChildNavigation } from '../../organisms/ChildNavigation' +import { MediumButton, SmallButton } from '../../../atoms/buttons' +import { OddModal } from '../../../molecules/OddModal' +import { ChildNavigation } from '../../../organisms/ChildNavigation' import { getResetConfigOptions, fetchResetConfigOptions, resetConfig, -} from '../../redux/robot-admin' -import { useDispatchApiRequest } from '../../redux/robot-api' +} from '../../../redux/robot-admin' +import { useDispatchApiRequest } from '../../../redux/robot-api' -import type { Dispatch, State } from '../../redux/types' -import type { ResetConfigRequest } from '../../redux/robot-admin/types' -import type { SetSettingOption } from '../../pages/ODD/RobotSettingsDashboard' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { Dispatch, State } from '../../../redux/types' +import type { ResetConfigRequest } from '../../../redux/robot-admin/types' +import type { SetSettingOption } from './types' +import type { OddModalHeaderBaseProps } from '../../../molecules/OddModal/types' interface LabelProps { isSelected?: boolean diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/EthernetConnectionDetails.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/EthernetConnectionDetails.tsx similarity index 93% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/EthernetConnectionDetails.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/EthernetConnectionDetails.tsx index f76c04fdf31..2ccb0f56acf 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/EthernetConnectionDetails.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/EthernetConnectionDetails.tsx @@ -15,11 +15,11 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { ChildNavigation } from '../../../organisms/ChildNavigation' -import { getNetworkInterfaces } from '../../../redux/networking' -import { getLocalRobot } from '../../../redux/discovery' +import { ChildNavigation } from '../../../../organisms/ChildNavigation' +import { getNetworkInterfaces } from '../../../../redux/networking' +import { getLocalRobot } from '../../../../redux/discovery' -import type { State } from '../../../redux/types' +import type { State } from '../../../../redux/types' const STRETCH_LIST_STYLE = css` width: 100%; diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal.tsx similarity index 94% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal.tsx index 980bf9e5991..9bff7c0c26f 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal.tsx @@ -14,9 +14,9 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { OddModal } from '../../../molecules/OddModal' +import { OddModal } from '../../../../molecules/OddModal' -import type { OddModalHeaderBaseProps } from '../../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '../../../../molecules/OddModal/types' interface NetworkDetailsModalProps { setShowNetworkDetailModal: (showNetworkDetailModal: boolean) => void diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsJoinOtherNetwork.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsJoinOtherNetwork.tsx similarity index 86% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsJoinOtherNetwork.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsJoinOtherNetwork.tsx index 98fff23fd96..9c83651b76b 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsJoinOtherNetwork.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsJoinOtherNetwork.tsx @@ -3,10 +3,10 @@ import { useTranslation } from 'react-i18next' import { DIRECTION_COLUMN, Flex } from '@opentrons/components' -import { ChildNavigation } from '../../../organisms/ChildNavigation' -import { SetWifiSsid } from '../../../organisms/NetworkSettings/SetWifiSsid' +import { ChildNavigation } from '../../../../organisms/ChildNavigation' +import { SetWifiSsid } from '../../NetworkSettings' -import type { SetSettingOption } from '../../../pages/ODD/RobotSettingsDashboard' +import type { SetSettingOption } from '../types' interface RobotSettingsJoinOtherNetworkProps { setCurrentOption: SetSettingOption diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsSelectAuthenticationType.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsSelectAuthenticationType.tsx similarity index 84% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsSelectAuthenticationType.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsSelectAuthenticationType.tsx index 4b64fa7c9ec..be9988ef579 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsSelectAuthenticationType.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsSelectAuthenticationType.tsx @@ -3,11 +3,11 @@ import { useTranslation } from 'react-i18next' import { DIRECTION_COLUMN, Flex } from '@opentrons/components' -import { ChildNavigation } from '../../../organisms/ChildNavigation' -import { SelectAuthenticationType } from '../../../organisms/NetworkSettings/SelectAuthenticationType' +import { ChildNavigation } from '../../../../organisms/ChildNavigation' +import { SelectAuthenticationType } from '../../NetworkSettings' import type { WifiSecurityType } from '@opentrons/api-client' -import type { SetSettingOption } from '../../../pages/ODD/RobotSettingsDashboard' +import type { SetSettingOption } from '../types' interface RobotSettingsSelectAuthenticationTypeProps { handleWifiConnect: () => void diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsSetWifiCred.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsSetWifiCred.tsx similarity index 81% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsSetWifiCred.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsSetWifiCred.tsx index cca6fe55252..68fa084b4a7 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsSetWifiCred.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsSetWifiCred.tsx @@ -3,10 +3,10 @@ import { useTranslation } from 'react-i18next' import { DIRECTION_COLUMN, Flex } from '@opentrons/components' -import { ChildNavigation } from '../../../organisms/ChildNavigation' -import { SetWifiCred } from '../../../organisms/NetworkSettings/SetWifiCred' +import { ChildNavigation } from '../../../../organisms/ChildNavigation' +import { SetWifiCred } from '../../NetworkSettings/SetWifiCred' -import type { SetSettingOption } from '../../../pages/ODD/RobotSettingsDashboard' +import type { SetSettingOption } from '../types' interface RobotSettingsSetWifiCredProps { handleConnect: () => void diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifi.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifi.tsx similarity index 90% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifi.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifi.tsx index 92783005b9d..d1f05b10894 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifi.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifi.tsx @@ -3,11 +3,11 @@ import { useTranslation } from 'react-i18next' import { DIRECTION_COLUMN, Flex } from '@opentrons/components' -import { ChildNavigation } from '../../../organisms/ChildNavigation' +import { ChildNavigation } from '../../../../organisms/ChildNavigation' import { WifiConnectionDetails } from './WifiConnectionDetails' import type { WifiSecurityType } from '@opentrons/api-client' -import type { SetSettingOption } from '../../../pages/ODD/RobotSettingsDashboard' +import type { SetSettingOption } from '../types' interface RobotSettingsWifiProps { setSelectedSsid: React.Dispatch> diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifiConnect.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifiConnect.tsx similarity index 84% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifiConnect.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifiConnect.tsx index bdfd52a4c3e..4fb0510b5ff 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifiConnect.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifiConnect.tsx @@ -3,15 +3,12 @@ import { useTranslation } from 'react-i18next' import { Flex, DIRECTION_COLUMN, SPACING } from '@opentrons/components' -import { ChildNavigation } from '../../../organisms/ChildNavigation' -import { - ConnectingNetwork, - FailedToConnect, -} from '../../../organisms/NetworkSettings' -import { FAILURE, PENDING, SUCCESS } from '../../../redux/robot-api' +import { ChildNavigation } from '../../../../organisms/ChildNavigation' +import { ConnectingNetwork, FailedToConnect } from '../../NetworkSettings' +import { FAILURE, PENDING, SUCCESS } from '../../../../redux/robot-api' -import type { SetSettingOption } from '../../../pages/ODD/RobotSettingsDashboard' -import type { RequestState } from '../../../redux/robot-api/types' +import type { SetSettingOption } from '../types' +import type { RequestState } from '../../../../redux/robot-api/types' interface RobotSettingsWifiConnectProps { handleConnect: () => void diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/WifiConnectionDetails.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/WifiConnectionDetails.tsx similarity index 95% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/WifiConnectionDetails.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/WifiConnectionDetails.tsx index 4c944458be2..e4ce4db581e 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/WifiConnectionDetails.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/WifiConnectionDetails.tsx @@ -20,12 +20,12 @@ import { import { NetworkDetailsModal } from './NetworkDetailsModal' import { DisplayWifiList } from '../../NetworkSettings' -import { getLocalRobot } from '../../../redux/discovery' -import { getNetworkInterfaces } from '../../../redux/networking' -import { useWifiList } from '../../../resources/networking/hooks' +import { getLocalRobot } from '../../../../redux/discovery' +import { getNetworkInterfaces } from '../../../../redux/networking' +import { useWifiList } from '../../../../resources/networking/hooks' import type { WifiSecurityType } from '@opentrons/api-client' -import type { State } from '../../../redux/types' +import type { State } from '../../../../redux/types' const FETCH_WIFI_LIST_MS = 5000 diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/EthernetConnectionDetails.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/EthernetConnectionDetails.test.tsx similarity index 81% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/EthernetConnectionDetails.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/EthernetConnectionDetails.test.tsx index a82b17e3455..9f97bddebb3 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/EthernetConnectionDetails.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/EthernetConnectionDetails.test.tsx @@ -3,17 +3,17 @@ import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../../i18n' -import { INTERFACE_ETHERNET } from '../../../../redux/networking' -import { getNetworkInterfaces } from '../../../../redux/networking/selectors' -import { renderWithProviders } from '../../../../__testing-utils__' -import { getLocalRobot } from '../../../../redux/discovery' -import { mockConnectedRobot } from '../../../../redux/discovery/__fixtures__' +import { i18n } from '../../../../../i18n' +import { INTERFACE_ETHERNET } from '../../../../../redux/networking' +import { getNetworkInterfaces } from '../../../../../redux/networking/selectors' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { getLocalRobot } from '../../../../../redux/discovery' +import { mockConnectedRobot } from '../../../../../redux/discovery/__fixtures__' import { EthernetConnectionDetails } from '../EthernetConnectionDetails' -vi.mock('../../../../redux/discovery') -vi.mock('../../../../redux/discovery/selectors') -vi.mock('../../../../redux/networking/selectors') +vi.mock('../../../../../redux/discovery') +vi.mock('../../../../../redux/discovery/selectors') +vi.mock('../../../../../redux/networking/selectors') const render = ( props: React.ComponentProps diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkDetailsModal.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkDetailsModal.test.tsx similarity index 95% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkDetailsModal.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkDetailsModal.test.tsx index 6333bec9b81..1c7766156ee 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkDetailsModal.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkDetailsModal.test.tsx @@ -3,8 +3,8 @@ import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../../i18n' -import { renderWithProviders } from '../../../../__testing-utils__' +import { i18n } from '../../../../../i18n' +import { renderWithProviders } from '../../../../../__testing-utils__' import { NetworkDetailsModal } from '../NetworkDetailsModal' const mockFn = vi.fn() diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkSettings.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkSettings.test.tsx similarity index 85% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkSettings.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkSettings.test.tsx index 5d27163d300..c0af9d3e2d4 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkSettings.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkSettings.test.tsx @@ -4,19 +4,19 @@ import { screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../../i18n' -import { renderWithProviders } from '../../../../__testing-utils__' -import { getLocalRobot } from '../../../../redux/discovery' -import { useWifiList } from '../../../../resources/networking/hooks' +import { i18n } from '../../../../../i18n' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { getLocalRobot } from '../../../../../redux/discovery' +import { useWifiList } from '../../../../../resources/networking/hooks' import { WifiConnectionDetails } from '../WifiConnectionDetails' import { EthernetConnectionDetails } from '../EthernetConnectionDetails' import { NetworkSettings } from '..' -import type { DiscoveredRobot } from '../../../../redux/discovery/types' -import type { WifiNetwork } from '../../../../redux/networking/types' +import type { DiscoveredRobot } from '../../../../../redux/discovery/types' +import type { WifiNetwork } from '../../../../../redux/networking/types' -vi.mock('../../../../redux/discovery') -vi.mock('../../../../resources/networking/hooks') +vi.mock('../../../../../redux/discovery') +vi.mock('../../../../../resources/networking/hooks') vi.mock('../WifiConnectionDetails') vi.mock('../EthernetConnectionDetails') diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx similarity index 87% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx index 02da87250d3..aa2ca76572b 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx @@ -4,17 +4,17 @@ import { when } from 'vitest-when' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../../i18n' -import { renderWithProviders } from '../../../../__testing-utils__' -import { getLocalRobot } from '../../../../redux/discovery' -import * as Networking from '../../../../redux/networking' +import { i18n } from '../../../../../i18n' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { getLocalRobot } from '../../../../../redux/discovery' +import * as Networking from '../../../../../redux/networking' import { NetworkDetailsModal } from '../NetworkDetailsModal' import { WifiConnectionDetails } from '../WifiConnectionDetails' import type * as Dom from 'react-router-dom' -import type { State } from '../../../../redux/types' +import type { State } from '../../../../../redux/types' -vi.mock('../../../../redux/discovery') -vi.mock('../../../../redux/networking') +vi.mock('../../../../../redux/discovery') +vi.mock('../../../../../redux/networking') vi.mock('../NetworkDetailsModal') const mockNavigate = vi.fn() diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/index.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/index.tsx similarity index 95% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/index.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/index.tsx index f024a572f0f..dfe066aeb54 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/index.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/index.tsx @@ -17,11 +17,11 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { ChildNavigation } from '../../../organisms/ChildNavigation' +import { ChildNavigation } from '../../../../organisms/ChildNavigation' import type { IconName, ChipType } from '@opentrons/components' -import type { NetworkConnection } from '../../../resources/networking/hooks/useNetworkConnection' -import type { SetSettingOption } from '../../../pages/ODD/RobotSettingsDashboard' +import type { NetworkConnection } from '../../../../resources/networking/hooks/useNetworkConnection' +import type { SetSettingOption } from '../types' export type ConnectionType = 'wifi' | 'ethernet' | 'usb' diff --git a/app/src/organisms/ODD/RobotSettingsDashboard/OnOffToggle.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/OnOffToggle.tsx new file mode 100644 index 00000000000..565a8fb0798 --- /dev/null +++ b/app/src/organisms/ODD/RobotSettingsDashboard/OnOffToggle.tsx @@ -0,0 +1,30 @@ +import * as React from 'react' +import { useTranslation } from 'react-i18next' +import { + ALIGN_CENTER, + BORDERS, + COLORS, + DIRECTION_ROW, + Flex, + LegacyStyledText, + SPACING, + TYPOGRAPHY, +} from '@opentrons/components' + +export function OnOffToggle(props: { isOn: boolean }): JSX.Element { + const { t } = useTranslation('shared') + return ( + + + {props.isOn ? t('on') : t('off')} + + + ) +} diff --git a/app/src/organisms/RobotSettingsDashboard/Privacy.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/Privacy.tsx similarity index 80% rename from app/src/organisms/RobotSettingsDashboard/Privacy.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/Privacy.tsx index e52432bd765..117b57d8cc9 100644 --- a/app/src/organisms/RobotSettingsDashboard/Privacy.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/Privacy.tsx @@ -10,16 +10,16 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { ChildNavigation } from '../../organisms/ChildNavigation' -import { RobotSettingButton } from '../../pages/ODD/RobotSettingsDashboard/RobotSettingButton' -import { OnOffToggle } from '../../pages/ODD/RobotSettingsDashboard/RobotSettingsList' +import { ChildNavigation } from '../../../organisms/ChildNavigation' +import { RobotSettingButton } from './RobotSettingButton' +import { OnOffToggle } from './OnOffToggle' import { getAnalyticsOptedIn, toggleAnalyticsOptedIn, -} from '../../redux/analytics' +} from '../../../redux/analytics' -import type { Dispatch } from '../../redux/types' -import type { SetSettingOption } from '../../pages/ODD/RobotSettingsDashboard' +import type { Dispatch } from '../../../redux/types' +import type { SetSettingOption } from './types' interface PrivacyProps { robotName: string diff --git a/app/src/organisms/RobotSettingsDashboard/RobotName.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/RobotName.tsx similarity index 91% rename from app/src/organisms/RobotSettingsDashboard/RobotName.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/RobotName.tsx index 39be60d9fb0..cb321fdaf95 100644 --- a/app/src/organisms/RobotSettingsDashboard/RobotName.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/RobotName.tsx @@ -10,7 +10,7 @@ import { LegacyStyledText, } from '@opentrons/components' -import type { SetSettingOption } from '../../pages/ODD/RobotSettingsDashboard' +import type { SetSettingOption } from './types' interface RobotNameProps { setCurrentOption: SetSettingOption diff --git a/app/src/pages/ODD/RobotSettingsDashboard/RobotSettingButton.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/RobotSettingButton.tsx similarity index 100% rename from app/src/pages/ODD/RobotSettingsDashboard/RobotSettingButton.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/RobotSettingButton.tsx diff --git a/app/src/organisms/RobotSettingsDashboard/RobotSystemVersion.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/RobotSystemVersion.tsx similarity index 91% rename from app/src/organisms/RobotSettingsDashboard/RobotSystemVersion.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/RobotSystemVersion.tsx index f3cf64289ca..254d45b829d 100644 --- a/app/src/organisms/RobotSettingsDashboard/RobotSystemVersion.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/RobotSystemVersion.tsx @@ -13,12 +13,12 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { MediumButton } from '../../atoms/buttons' -import { ChildNavigation } from '../../organisms/ChildNavigation' +import { MediumButton } from '../../../atoms/buttons' +import { ChildNavigation } from '../../../organisms/ChildNavigation' import { RobotSystemVersionModal } from './RobotSystemVersionModal' -import type { RobotUpdateInfo } from '../../redux/robot-update/types' -import type { SetSettingOption } from '../../pages/ODD/RobotSettingsDashboard' +import type { RobotUpdateInfo } from '../../../redux/robot-update/types' +import type { SetSettingOption } from './types' const GITHUB_URL = 'https://github.com/Opentrons/opentrons/releases' diff --git a/app/src/organisms/RobotSettingsDashboard/RobotSystemVersionModal.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/RobotSystemVersionModal.tsx similarity index 84% rename from app/src/organisms/RobotSettingsDashboard/RobotSystemVersionModal.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/RobotSystemVersionModal.tsx index 028ca40b1cc..407ff8cb8dd 100644 --- a/app/src/organisms/RobotSettingsDashboard/RobotSystemVersionModal.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/RobotSystemVersionModal.tsx @@ -10,12 +10,12 @@ import { SPACING, } from '@opentrons/components' -import { SmallButton } from '../../atoms/buttons' -import { InlineNotification } from '../../atoms/InlineNotification' -import { ReleaseNotes } from '../../molecules/ReleaseNotes' -import { OddModal } from '../../molecules/OddModal' +import { SmallButton } from '../../../atoms/buttons' +import { InlineNotification } from '../../../atoms/InlineNotification' +import { ReleaseNotes } from '../../../molecules/ReleaseNotes' +import { OddModal } from '../../../molecules/OddModal' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '../../../molecules/OddModal/types' interface RobotSystemVersionModalProps { version: string diff --git a/app/src/organisms/RobotSettingsDashboard/TextSize.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/TextSize.tsx similarity index 97% rename from app/src/organisms/RobotSettingsDashboard/TextSize.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/TextSize.tsx index b81cfd88a68..ad71888349f 100644 --- a/app/src/organisms/RobotSettingsDashboard/TextSize.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/TextSize.tsx @@ -18,7 +18,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import type { SetSettingOption } from '../../pages/ODD/RobotSettingsDashboard' +import type { SetSettingOption } from './types' interface RectProps { isActive: boolean diff --git a/app/src/organisms/RobotSettingsDashboard/TouchScreenSleep.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/TouchScreenSleep.tsx similarity index 89% rename from app/src/organisms/RobotSettingsDashboard/TouchScreenSleep.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/TouchScreenSleep.tsx index c07de2d0a79..627c9842547 100644 --- a/app/src/organisms/RobotSettingsDashboard/TouchScreenSleep.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/TouchScreenSleep.tsx @@ -9,15 +9,15 @@ import { RadioButton, } from '@opentrons/components' -import { ChildNavigation } from '../../organisms/ChildNavigation' +import { ChildNavigation } from '../../../organisms/ChildNavigation' import { getOnDeviceDisplaySettings, updateConfigValue, -} from '../../redux/config' -import { SLEEP_NEVER_MS } from '../../App/constants' +} from '../../../redux/config' +import { SLEEP_NEVER_MS } from '../../../App/constants' -import type { Dispatch } from '../../redux/types' -import type { SetSettingOption } from '../../pages/ODD/RobotSettingsDashboard' +import type { Dispatch } from '../../../redux/types' +import type { SetSettingOption } from './types' const SLEEP_TIME_MS = 60 * 1000 // 1 min diff --git a/app/src/organisms/RobotSettingsDashboard/TouchscreenBrightness.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/TouchscreenBrightness.tsx similarity index 91% rename from app/src/organisms/RobotSettingsDashboard/TouchscreenBrightness.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/TouchscreenBrightness.tsx index 4055ba07bd6..6f466ea7f95 100644 --- a/app/src/organisms/RobotSettingsDashboard/TouchscreenBrightness.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/TouchscreenBrightness.tsx @@ -16,15 +16,15 @@ import { SPACING, } from '@opentrons/components' -import { ChildNavigation } from '../../organisms/ChildNavigation' +import { ChildNavigation } from '../../../organisms/ChildNavigation' import { getOnDeviceDisplaySettings, updateConfigValue, -} from '../../redux/config' +} from '../../../redux/config' -import type { Dispatch } from '../../redux/types' -import type { SetSettingOption } from '../../pages/ODD/RobotSettingsDashboard' -import { IconButton } from '../../atoms/buttons/IconButton' +import type { Dispatch } from '../../../redux/types' +import type { SetSettingOption } from './types' +import { IconButton } from '../../../atoms/buttons/IconButton' interface BrightnessTileProps { isActive: boolean diff --git a/app/src/organisms/RobotSettingsDashboard/UpdateChannel.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/UpdateChannel.tsx similarity index 96% rename from app/src/organisms/RobotSettingsDashboard/UpdateChannel.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/UpdateChannel.tsx index 5c3bd7f8737..d1fa185bb32 100644 --- a/app/src/organisms/RobotSettingsDashboard/UpdateChannel.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/UpdateChannel.tsx @@ -13,15 +13,15 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { ChildNavigation } from '../../organisms/ChildNavigation' +import { ChildNavigation } from '../../../organisms/ChildNavigation' import { getDevtoolsEnabled, getUpdateChannel, getUpdateChannelOptions, updateConfigValue, -} from '../../redux/config' +} from '../../../redux/config' -import type { Dispatch } from '../../redux/types' +import type { Dispatch } from '../../../redux/types' interface LabelProps { isSelected?: boolean diff --git a/app/src/organisms/RobotSettingsDashboard/__tests__/DeviceReset.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/DeviceReset.test.tsx similarity index 93% rename from app/src/organisms/RobotSettingsDashboard/__tests__/DeviceReset.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/__tests__/DeviceReset.test.tsx index 2d6fd56f066..d6473d83e85 100644 --- a/app/src/organisms/RobotSettingsDashboard/__tests__/DeviceReset.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/DeviceReset.test.tsx @@ -2,17 +2,20 @@ import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' -import { getResetConfigOptions, resetConfig } from '../../../redux/robot-admin' -import { useDispatchApiRequest } from '../../../redux/robot-api' +import { i18n } from '../../../../i18n' +import { renderWithProviders } from '../../../../__testing-utils__' +import { + getResetConfigOptions, + resetConfig, +} from '../../../../redux/robot-admin' +import { useDispatchApiRequest } from '../../../../redux/robot-api' import { DeviceReset } from '../DeviceReset' -import type { DispatchApiRequestType } from '../../../redux/robot-api' +import type { DispatchApiRequestType } from '../../../../redux/robot-api' -vi.mock('../../../redux/robot-admin') -vi.mock('../../../redux/robot-api') +vi.mock('../../../../redux/robot-admin') +vi.mock('../../../../redux/robot-api') const mockResetConfigOptions = [ { diff --git a/app/src/organisms/RobotSettingsDashboard/__tests__/Privacy.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/Privacy.test.tsx similarity index 78% rename from app/src/organisms/RobotSettingsDashboard/__tests__/Privacy.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/__tests__/Privacy.test.tsx index 6e1a12878e3..551b04c8acb 100644 --- a/app/src/organisms/RobotSettingsDashboard/__tests__/Privacy.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/Privacy.test.tsx @@ -2,15 +2,15 @@ import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { vi, describe, beforeEach, afterEach, expect, it } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { toggleAnalyticsOptedIn } from '../../../redux/analytics' -import { getRobotSettings } from '../../../redux/robot-settings' +import { renderWithProviders } from '../../../../__testing-utils__' +import { i18n } from '../../../../i18n' +import { toggleAnalyticsOptedIn } from '../../../../redux/analytics' +import { getRobotSettings } from '../../../../redux/robot-settings' import { Privacy } from '../Privacy' -vi.mock('../../../redux/analytics') -vi.mock('../../../redux/robot-settings') +vi.mock('../../../../redux/analytics') +vi.mock('../../../../redux/robot-settings') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/RobotSettingsDashboard/__tests__/RobotSystemVersion.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/RobotSystemVersion.test.tsx similarity index 93% rename from app/src/organisms/RobotSettingsDashboard/__tests__/RobotSystemVersion.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/__tests__/RobotSystemVersion.test.tsx index c7ddba35831..8665ed09405 100644 --- a/app/src/organisms/RobotSettingsDashboard/__tests__/RobotSystemVersion.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/RobotSystemVersion.test.tsx @@ -4,12 +4,12 @@ import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '../../../../i18n' +import { renderWithProviders } from '../../../../__testing-utils__' import { RobotSystemVersion } from '../RobotSystemVersion' import { RobotSystemVersionModal } from '../RobotSystemVersionModal' -vi.mock('../../../redux/shell') +vi.mock('../../../../redux/shell') vi.mock('../RobotSystemVersionModal') const mockBack = vi.fn() diff --git a/app/src/organisms/RobotSettingsDashboard/__tests__/RobotSystemVersionModal.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/RobotSystemVersionModal.test.tsx similarity index 94% rename from app/src/organisms/RobotSettingsDashboard/__tests__/RobotSystemVersionModal.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/__tests__/RobotSystemVersionModal.test.tsx index 887b36c332b..9eaa93aabf1 100644 --- a/app/src/organisms/RobotSettingsDashboard/__tests__/RobotSystemVersionModal.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/RobotSystemVersionModal.test.tsx @@ -3,8 +3,8 @@ import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '../../../../i18n' +import { renderWithProviders } from '../../../../__testing-utils__' import { RobotSystemVersionModal } from '../RobotSystemVersionModal' import type * as Dom from 'react-router-dom' diff --git a/app/src/organisms/RobotSettingsDashboard/__tests__/TextSize.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/TextSize.test.tsx similarity index 91% rename from app/src/organisms/RobotSettingsDashboard/__tests__/TextSize.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/__tests__/TextSize.test.tsx index bbe8ccba0d7..ca555bc096d 100644 --- a/app/src/organisms/RobotSettingsDashboard/__tests__/TextSize.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/TextSize.test.tsx @@ -2,8 +2,8 @@ import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '../../../../i18n' +import { renderWithProviders } from '../../../../__testing-utils__' import { TextSize } from '../TextSize' const mockFunc = vi.fn() diff --git a/app/src/organisms/RobotSettingsDashboard/__tests__/TouchScreenSleep.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/TouchScreenSleep.test.tsx similarity index 86% rename from app/src/organisms/RobotSettingsDashboard/__tests__/TouchScreenSleep.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/__tests__/TouchScreenSleep.test.tsx index c27c2fd112b..65bd35ce0cd 100644 --- a/app/src/organisms/RobotSettingsDashboard/__tests__/TouchScreenSleep.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/TouchScreenSleep.test.tsx @@ -1,12 +1,12 @@ import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' -import { i18n } from '../../../i18n' -import { updateConfigValue } from '../../../redux/config' +import { i18n } from '../../../../i18n' +import { updateConfigValue } from '../../../../redux/config' import { TouchScreenSleep } from '../TouchScreenSleep' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '../../../../__testing-utils__' -vi.mock('../../../redux/config') +vi.mock('../../../../redux/config') // Note (kj:06/28/2023) this line is to avoid causing errors for scrollIntoView window.HTMLElement.prototype.scrollIntoView = vi.fn() diff --git a/app/src/organisms/RobotSettingsDashboard/__tests__/TouchscreenBrightness.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/TouchscreenBrightness.test.tsx similarity index 92% rename from app/src/organisms/RobotSettingsDashboard/__tests__/TouchscreenBrightness.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/__tests__/TouchscreenBrightness.test.tsx index dce842b1691..a3d20baf2cf 100644 --- a/app/src/organisms/RobotSettingsDashboard/__tests__/TouchscreenBrightness.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/TouchscreenBrightness.test.tsx @@ -2,15 +2,15 @@ import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' -import { i18n } from '../../../i18n' +import { i18n } from '../../../../i18n' import { getOnDeviceDisplaySettings, updateConfigValue, -} from '../../../redux/config' -import { renderWithProviders } from '../../../__testing-utils__' +} from '../../../../redux/config' +import { renderWithProviders } from '../../../../__testing-utils__' import { TouchscreenBrightness } from '../TouchscreenBrightness' -vi.mock('../../../redux/config') +vi.mock('../../../../redux/config') const mockFunc = vi.fn() diff --git a/app/src/organisms/RobotSettingsDashboard/__tests__/UpdateChannel.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/UpdateChannel.test.tsx similarity index 92% rename from app/src/organisms/RobotSettingsDashboard/__tests__/UpdateChannel.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/__tests__/UpdateChannel.test.tsx index 7ed9db3fc0f..cb1a703a0e3 100644 --- a/app/src/organisms/RobotSettingsDashboard/__tests__/UpdateChannel.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/UpdateChannel.test.tsx @@ -3,17 +3,17 @@ import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../i18n' +import { i18n } from '../../../../i18n' import { getDevtoolsEnabled, getUpdateChannelOptions, updateConfigValue, -} from '../../../redux/config' -import { renderWithProviders } from '../../../__testing-utils__' +} from '../../../../redux/config' +import { renderWithProviders } from '../../../../__testing-utils__' import { UpdateChannel } from '../UpdateChannel' -vi.mock('../../../redux/config') +vi.mock('../../../../redux/config') const mockChannelOptions = [ { diff --git a/app/src/organisms/RobotSettingsDashboard/index.ts b/app/src/organisms/ODD/RobotSettingsDashboard/index.ts similarity index 86% rename from app/src/organisms/RobotSettingsDashboard/index.ts rename to app/src/organisms/ODD/RobotSettingsDashboard/index.ts index ba05950ff24..30933095135 100644 --- a/app/src/organisms/RobotSettingsDashboard/index.ts +++ b/app/src/organisms/ODD/RobotSettingsDashboard/index.ts @@ -12,3 +12,6 @@ export * from './TextSize' export * from './TouchscreenBrightness' export * from './TouchScreenSleep' export * from './UpdateChannel' +export * from './RobotSettingButton' +export * from './OnOffToggle' +export type * from './types' diff --git a/app/src/organisms/ODD/RobotSettingsDashboard/types.ts b/app/src/organisms/ODD/RobotSettingsDashboard/types.ts new file mode 100644 index 00000000000..231d26c837b --- /dev/null +++ b/app/src/organisms/ODD/RobotSettingsDashboard/types.ts @@ -0,0 +1,21 @@ +/** + * a set of screen options for the robot settings dashboard page + */ +export type SettingOption = + | 'NetworkSettings' + | 'RobotName' + | 'RobotSystemVersion' + | 'TouchscreenSleep' + | 'TouchscreenBrightness' + | 'TextSize' + | 'Privacy' + | 'DeviceReset' + | 'UpdateChannel' + | 'EthernetConnectionDetails' + | 'RobotSettingsSelectAuthenticationType' + | 'RobotSettingsJoinOtherNetwork' + | 'RobotSettingsSetWifiCred' + | 'RobotSettingsWifi' + | 'RobotSettingsWifiConnect' + +export type SetSettingOption = (option: SettingOption | null) => void diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/hooks.test.tsx b/app/src/organisms/ODD/hooks/__tests__/useIsUnboxingFlowOngoing.test.tsx similarity index 96% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/hooks.test.tsx rename to app/src/organisms/ODD/hooks/__tests__/useIsUnboxingFlowOngoing.test.tsx index 223c2d25457..d7c6e4c8db7 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/hooks.test.tsx +++ b/app/src/organisms/ODD/hooks/__tests__/useIsUnboxingFlowOngoing.test.tsx @@ -8,7 +8,7 @@ import { getIsOnDevice, getOnDeviceDisplaySettings, } from '../../../../redux/config' -import { useIsUnboxingFlowOngoing } from '../hooks' +import { useIsUnboxingFlowOngoing } from '../useIsUnboxingFlowOngoing' import type { Store } from 'redux' import type { State } from '../../../../redux/types' diff --git a/app/src/organisms/ODD/hooks/index.ts b/app/src/organisms/ODD/hooks/index.ts new file mode 100644 index 00000000000..9781d731120 --- /dev/null +++ b/app/src/organisms/ODD/hooks/index.ts @@ -0,0 +1 @@ +export * from './useIsUnboxingFlowOngoing' diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/hooks.ts b/app/src/organisms/ODD/hooks/useIsUnboxingFlowOngoing.ts similarity index 100% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/hooks.ts rename to app/src/organisms/ODD/hooks/useIsUnboxingFlowOngoing.ts diff --git a/app/src/pages/ODD/ConnectViaEthernet/index.tsx b/app/src/pages/ODD/ConnectViaEthernet/index.tsx index 1f038413ca5..5cede84110f 100644 --- a/app/src/pages/ODD/ConnectViaEthernet/index.tsx +++ b/app/src/pages/ODD/ConnectViaEthernet/index.tsx @@ -10,7 +10,7 @@ import { } from '@opentrons/components' import { StepMeter } from '../../../atoms/StepMeter' -import { NetworkDetailsModal } from '../../../organisms/RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal' +import { NetworkDetailsModal } from '../../../organisms/ODD/RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal' import { getNetworkInterfaces, fetchStatus } from '../../../redux/networking' import { getLocalRobot } from '../../../redux/discovery' import { TitleHeader } from './TitleHeader' diff --git a/app/src/pages/ODD/ConnectViaWifi/JoinOtherNetwork.tsx b/app/src/pages/ODD/ConnectViaWifi/JoinOtherNetwork.tsx index 2e5df745b63..25e663bd6a0 100644 --- a/app/src/pages/ODD/ConnectViaWifi/JoinOtherNetwork.tsx +++ b/app/src/pages/ODD/ConnectViaWifi/JoinOtherNetwork.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next' import { Flex, DIRECTION_COLUMN } from '@opentrons/components' -import { SetWifiSsid } from '../../../organisms/NetworkSettings' +import { SetWifiSsid } from '../../../organisms/ODD/NetworkSettings' import { RobotSetupHeader } from '../../../organisms/RobotSetupHeader' import type { WifiScreenOption } from './' diff --git a/app/src/pages/ODD/ConnectViaWifi/SelectAuthenticationType.tsx b/app/src/pages/ODD/ConnectViaWifi/SelectAuthenticationType.tsx index cf79e16affd..2355383d1c1 100644 --- a/app/src/pages/ODD/ConnectViaWifi/SelectAuthenticationType.tsx +++ b/app/src/pages/ODD/ConnectViaWifi/SelectAuthenticationType.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next' import { Flex, DIRECTION_COLUMN } from '@opentrons/components' -import { SelectAuthenticationType as SelectAuthenticationTypeComponent } from '../../../organisms/NetworkSettings' +import { SelectAuthenticationType as SelectAuthenticationTypeComponent } from '../../../organisms/ODD/NetworkSettings' import { RobotSetupHeader } from '../../../organisms/RobotSetupHeader' import type { WifiSecurityType } from '@opentrons/api-client' diff --git a/app/src/pages/ODD/ConnectViaWifi/SetWifiCred.tsx b/app/src/pages/ODD/ConnectViaWifi/SetWifiCred.tsx index 5a6c8824624..a1b76adb42a 100644 --- a/app/src/pages/ODD/ConnectViaWifi/SetWifiCred.tsx +++ b/app/src/pages/ODD/ConnectViaWifi/SetWifiCred.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next' import { Flex, DIRECTION_COLUMN } from '@opentrons/components' -import { SetWifiCred as SetWifiCredComponent } from '../../../organisms/NetworkSettings' +import { SetWifiCred as SetWifiCredComponent } from '../../../organisms/ODD/NetworkSettings' import { RobotSetupHeader } from '../../../organisms/RobotSetupHeader' import type { WifiScreenOption } from './' diff --git a/app/src/pages/ODD/ConnectViaWifi/WifiConnectStatus.tsx b/app/src/pages/ODD/ConnectViaWifi/WifiConnectStatus.tsx index cdd290cf7c6..3bf7cca8e58 100644 --- a/app/src/pages/ODD/ConnectViaWifi/WifiConnectStatus.tsx +++ b/app/src/pages/ODD/ConnectViaWifi/WifiConnectStatus.tsx @@ -7,7 +7,7 @@ import { ConnectingNetwork, FailedToConnect, WifiConnectionDetails, -} from '../../../organisms/NetworkSettings' +} from '../../../organisms/ODD/NetworkSettings' import { RobotSetupHeader } from '../../../organisms/RobotSetupHeader' import * as RobotApi from '../../../redux/robot-api' diff --git a/app/src/pages/ODD/ConnectViaWifi/index.tsx b/app/src/pages/ODD/ConnectViaWifi/index.tsx index 4d495174a57..2207b17e386 100644 --- a/app/src/pages/ODD/ConnectViaWifi/index.tsx +++ b/app/src/pages/ODD/ConnectViaWifi/index.tsx @@ -5,7 +5,7 @@ import last from 'lodash/last' import { Flex, DIRECTION_COLUMN, SPACING } from '@opentrons/components' import { StepMeter } from '../../../atoms/StepMeter' -import { DisplayWifiList } from '../../../organisms/NetworkSettings' +import { DisplayWifiList } from '../../../organisms/ODD/NetworkSettings' import * as Networking from '../../../redux/networking' import { getLocalRobot } from '../../../redux/discovery' import * as RobotApi from '../../../redux/robot-api' diff --git a/app/src/pages/ODD/NameRobot/__tests__/NameRobot.test.tsx b/app/src/pages/ODD/NameRobot/__tests__/NameRobot.test.tsx index 0ef5aaf869c..0605da1b768 100644 --- a/app/src/pages/ODD/NameRobot/__tests__/NameRobot.test.tsx +++ b/app/src/pages/ODD/NameRobot/__tests__/NameRobot.test.tsx @@ -11,7 +11,7 @@ import { getReachableRobots, getUnreachableRobots, } from '../../../../redux/discovery' -import { useIsUnboxingFlowOngoing } from '../../../../organisms/RobotSettingsDashboard/NetworkSettings/hooks' +import { useIsUnboxingFlowOngoing } from '../../../../organisms/ODD/hooks' import { mockConnectableRobot, mockReachableRobot, @@ -24,7 +24,7 @@ import type { NavigateFunction } from 'react-router-dom' vi.mock('../../../../redux/discovery/selectors') vi.mock('../../../../redux/config') vi.mock('../../../../redux/analytics') -vi.mock('../../../../organisms/RobotSettingsDashboard/NetworkSettings/hooks') +vi.mock('../../../../organisms/ODD/hooks') const mockNavigate = vi.fn() diff --git a/app/src/pages/ODD/NameRobot/index.tsx b/app/src/pages/ODD/NameRobot/index.tsx index 4f0660b6384..a2ba5a918c0 100644 --- a/app/src/pages/ODD/NameRobot/index.tsx +++ b/app/src/pages/ODD/NameRobot/index.tsx @@ -35,7 +35,7 @@ import { useTrackEvent, ANALYTICS_RENAME_ROBOT } from '../../../redux/analytics' import { AlphanumericKeyboard } from '../../../atoms/SoftwareKeyboard' import { SmallButton } from '../../../atoms/buttons' import { StepMeter } from '../../../atoms/StepMeter' -import { useIsUnboxingFlowOngoing } from '../../../organisms/RobotSettingsDashboard/NetworkSettings/hooks' +import { useIsUnboxingFlowOngoing } from '../../../organisms/ODD/hooks' import { ConfirmRobotName } from '../../../organisms/ODD/NameRobot/ConfirmRobotName' import type { FieldError, Resolver } from 'react-hook-form' diff --git a/app/src/pages/ODD/RobotSettingsDashboard/RobotSettingsList.tsx b/app/src/pages/ODD/RobotSettingsDashboard/RobotSettingsList.tsx index 775a49eb901..6ea3cc37e6e 100644 --- a/app/src/pages/ODD/RobotSettingsDashboard/RobotSettingsList.tsx +++ b/app/src/pages/ODD/RobotSettingsDashboard/RobotSettingsList.tsx @@ -38,10 +38,13 @@ import { UNREACHABLE } from '../../../redux/discovery/constants' import { Navigation } from '../../../organisms/Navigation' import { useLEDLights } from '../../../organisms/Devices/hooks' import { useNetworkConnection } from '../../../resources/networking/hooks/useNetworkConnection' -import { RobotSettingButton } from './RobotSettingButton' +import { + RobotSettingButton, + OnOffToggle, +} from '../../../organisms/ODD/RobotSettingsDashboard' import type { Dispatch, State } from '../../../redux/types' -import type { SetSettingOption } from './' +import type { SetSettingOption } from '../../../organisms/ODD/RobotSettingsDashboard' const HOME_GANTRY_SETTING_ID = 'disableHomeOnBoot' interface RobotSettingsListProps { @@ -264,21 +267,3 @@ function FeatureFlags(): JSX.Element { ) } - -export function OnOffToggle(props: { isOn: boolean }): JSX.Element { - const { t } = useTranslation('shared') - return ( - - - {props.isOn ? t('on') : t('off')} - - - ) -} diff --git a/app/src/pages/ODD/RobotSettingsDashboard/__tests__/RobotSettingsDashboard.test.tsx b/app/src/pages/ODD/RobotSettingsDashboard/__tests__/RobotSettingsDashboard.test.tsx index 954b71812a0..78ba8bbd0b5 100644 --- a/app/src/pages/ODD/RobotSettingsDashboard/__tests__/RobotSettingsDashboard.test.tsx +++ b/app/src/pages/ODD/RobotSettingsDashboard/__tests__/RobotSettingsDashboard.test.tsx @@ -19,7 +19,7 @@ import { Privacy, RobotSystemVersion, UpdateChannel, -} from '../../../../organisms/RobotSettingsDashboard' +} from '../../../../organisms/ODD/RobotSettingsDashboard' import { getRobotUpdateAvailable } from '../../../../redux/robot-update' import { useNetworkConnection } from '../../../../resources/networking/hooks/useNetworkConnection' import { useLEDLights } from '../../../../organisms/Devices/hooks' @@ -32,14 +32,16 @@ vi.mock('../../../../redux/config') vi.mock('../../../../redux/robot-settings') vi.mock('../../../../resources/networking/hooks/useNetworkConnection') vi.mock('../../../../organisms/Navigation') -vi.mock('../../../../organisms/RobotSettingsDashboard/TouchScreenSleep') -vi.mock('../../../../organisms/RobotSettingsDashboard/NetworkSettings') -vi.mock('../../../../organisms/RobotSettingsDashboard/DeviceReset') -vi.mock('../../../../organisms/RobotSettingsDashboard/RobotSystemVersion') -vi.mock('../../../../organisms/RobotSettingsDashboard/TouchscreenBrightness') -vi.mock('../../../../organisms/RobotSettingsDashboard/UpdateChannel') +vi.mock('../../../../organisms/ODD/RobotSettingsDashboard/TouchScreenSleep') +vi.mock('../../../../organisms/ODD/RobotSettingsDashboard/NetworkSettings') +vi.mock('../../../../organisms/ODD/RobotSettingsDashboard/DeviceReset') +vi.mock('../../../../organisms/ODD/RobotSettingsDashboard/RobotSystemVersion') +vi.mock( + '../../../../organisms/ODD/RobotSettingsDashboard/TouchscreenBrightness' +) +vi.mock('../../../../organisms/ODD/RobotSettingsDashboard/UpdateChannel') vi.mock('../../../../organisms/Devices/hooks') -vi.mock('../../../../organisms/RobotSettingsDashboard/Privacy') +vi.mock('../../../../organisms/ODD/RobotSettingsDashboard/Privacy') const mockToggleLights = vi.fn() diff --git a/app/src/pages/ODD/RobotSettingsDashboard/index.tsx b/app/src/pages/ODD/RobotSettingsDashboard/index.tsx index 5b8149a0f17..e144111b579 100644 --- a/app/src/pages/ODD/RobotSettingsDashboard/index.tsx +++ b/app/src/pages/ODD/RobotSettingsDashboard/index.tsx @@ -3,7 +3,7 @@ import { useDispatch, useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import last from 'lodash/last' -import { EthernetConnectionDetails } from '../../../organisms/RobotSettingsDashboard/NetworkSettings/EthernetConnectionDetails' +import { EthernetConnectionDetails } from '../../../organisms/ODD/RobotSettingsDashboard/NetworkSettings/EthernetConnectionDetails' import { DeviceReset, TouchscreenBrightness, @@ -18,7 +18,7 @@ import { RobotSettingsWifiConnect, RobotSystemVersion, UpdateChannel, -} from '../../../organisms/RobotSettingsDashboard' +} from '../../../organisms/ODD/RobotSettingsDashboard' import { getRobotUpdateAvailable, getRobotUpdateInfoForRobot, @@ -36,28 +36,7 @@ import { RobotSettingsList } from './RobotSettingsList' import type { WifiSecurityType } from '@opentrons/api-client' import type { Dispatch, State } from '../../../redux/types' - -/** - * a set of screen options for the robot settings dashboard page - */ -export type SettingOption = - | 'NetworkSettings' - | 'RobotName' - | 'RobotSystemVersion' - | 'TouchscreenSleep' - | 'TouchscreenBrightness' - | 'TextSize' - | 'Privacy' - | 'DeviceReset' - | 'UpdateChannel' - | 'EthernetConnectionDetails' - | 'RobotSettingsSelectAuthenticationType' - | 'RobotSettingsJoinOtherNetwork' - | 'RobotSettingsSetWifiCred' - | 'RobotSettingsWifi' - | 'RobotSettingsWifiConnect' - -export type SetSettingOption = (option: SettingOption | null) => void +import type { SettingOption } from '../../../organisms/ODD/RobotSettingsDashboard' export function RobotSettingsDashboard(): JSX.Element { const { i18n, t } = useTranslation('shared') From 8560ddbfb2bf2d61e521cb7e6bb9696722facee6 Mon Sep 17 00:00:00 2001 From: Rhyann Clarke <146747548+rclarke0@users.noreply.github.com> Date: Wed, 18 Sep 2024 08:21:22 -0400 Subject: [PATCH 18/24] feat(abr-testing): Add recording for API 2.20 features - error recovery and liquid probing (#16236) # Overview Records occurrences of error recovery and liquid probing to ABR data tracking sheets. ## Test Plan and Hands on Testing ## Changelog ## Review requests ## Risk assessment --- abr-testing/Pipfile.lock | 1081 ++++++++--------- .../automation/google_sheets_tool.py | 11 +- .../abr_testing/automation/jira_tool.py | 8 +- .../data_collection/abr_google_drive.py | 24 +- .../data_collection/abr_robot_error.py | 23 +- .../data_collection/read_robot_logs.py | 122 +- .../abr_testing/tools/sync_abr_sheet.py | 13 +- 7 files changed, 655 insertions(+), 627 deletions(-) diff --git a/abr-testing/Pipfile.lock b/abr-testing/Pipfile.lock index ff84e2bfab3..05e3c72eeda 100644 --- a/abr-testing/Pipfile.lock +++ b/abr-testing/Pipfile.lock @@ -2,7 +2,6 @@ "_meta": { "hash": { "sha256": "b82b82f6cc192a520ccaa5f2c94a2dda16993ef31ebd9e140f85f80b6a96cc6a" - }, "pipfile-spec": 6, "requires": { @@ -21,93 +20,117 @@ "editable": true, "path": "." }, + "aiohappyeyeballs": { + "hashes": [ + "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2", + "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd" + ], + "markers": "python_version >= '3.8'", + "version": "==2.4.0" + }, "aiohttp": { "hashes": [ - "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8", - "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c", - "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475", - "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed", - "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf", - "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372", - "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81", - "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f", - "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1", - "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd", - "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a", - "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb", - "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46", - "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de", - "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78", - "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c", - "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771", - "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb", - "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430", - "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233", - "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156", - "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9", - "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59", - "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888", - "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c", - "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c", - "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da", - "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424", - "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2", - "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb", - "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8", - "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a", - "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10", - "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0", - "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09", - "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031", - "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4", - "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3", - "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa", - "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a", - "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe", - "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a", - "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2", - "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1", - "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323", - "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b", - "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b", - "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106", - "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac", - "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6", - "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832", - "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75", - "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6", - "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d", - "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72", - "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db", - "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a", - "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da", - "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678", - "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b", - "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24", - "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed", - "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f", - "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e", - "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58", - "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a", - "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342", - "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558", - "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2", - "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551", - "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595", - "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee", - "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11", - "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d", - "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7", - "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f" + "sha256:02594361128f780eecc2a29939d9dfc870e17b45178a867bf61a11b2a4367277", + "sha256:03f2645adbe17f274444953bdea69f8327e9d278d961d85657cb0d06864814c1", + "sha256:074d1bff0163e107e97bd48cad9f928fa5a3eb4b9d33366137ffce08a63e37fe", + "sha256:0912b8a8fadeb32ff67a3ed44249448c20148397c1ed905d5dac185b4ca547bb", + "sha256:0d277cfb304118079e7044aad0b76685d30ecb86f83a0711fc5fb257ffe832ca", + "sha256:0d93400c18596b7dc4794d48a63fb361b01a0d8eb39f28800dc900c8fbdaca91", + "sha256:123dd5b16b75b2962d0fff566effb7a065e33cd4538c1692fb31c3bda2bfb972", + "sha256:17e997105bd1a260850272bfb50e2a328e029c941c2708170d9d978d5a30ad9a", + "sha256:18a01eba2574fb9edd5f6e5fb25f66e6ce061da5dab5db75e13fe1558142e0a3", + "sha256:1923a5c44061bffd5eebeef58cecf68096e35003907d8201a4d0d6f6e387ccaa", + "sha256:1942244f00baaacaa8155eca94dbd9e8cc7017deb69b75ef67c78e89fdad3c77", + "sha256:1b2c16a919d936ca87a3c5f0e43af12a89a3ce7ccbce59a2d6784caba945b68b", + "sha256:1c19de68896747a2aa6257ae4cf6ef59d73917a36a35ee9d0a6f48cff0f94db8", + "sha256:1e72589da4c90337837fdfe2026ae1952c0f4a6e793adbbfbdd40efed7c63599", + "sha256:22c0a23a3b3138a6bf76fc553789cb1a703836da86b0f306b6f0dc1617398abc", + "sha256:2c634a3207a5445be65536d38c13791904fda0748b9eabf908d3fe86a52941cf", + "sha256:2d21ac12dc943c68135ff858c3a989f2194a709e6e10b4c8977d7fcd67dfd511", + "sha256:2f1f1c75c395991ce9c94d3e4aa96e5c59c8356a15b1c9231e783865e2772699", + "sha256:305be5ff2081fa1d283a76113b8df7a14c10d75602a38d9f012935df20731487", + "sha256:33e6bc4bab477c772a541f76cd91e11ccb6d2efa2b8d7d7883591dfb523e5987", + "sha256:349ef8a73a7c5665cca65c88ab24abe75447e28aa3bc4c93ea5093474dfdf0ff", + "sha256:380f926b51b92d02a34119d072f178d80bbda334d1a7e10fa22d467a66e494db", + "sha256:38172a70005252b6893088c0f5e8a47d173df7cc2b2bd88650957eb84fcf5022", + "sha256:391cc3a9c1527e424c6865e087897e766a917f15dddb360174a70467572ac6ce", + "sha256:3a1c32a19ee6bbde02f1cb189e13a71b321256cc1d431196a9f824050b160d5a", + "sha256:4120d7fefa1e2d8fb6f650b11489710091788de554e2b6f8347c7a20ceb003f5", + "sha256:424ae21498790e12eb759040bbb504e5e280cab64693d14775c54269fd1d2bb7", + "sha256:44b324a6b8376a23e6ba25d368726ee3bc281e6ab306db80b5819999c737d820", + "sha256:4790f0e15f00058f7599dab2b206d3049d7ac464dc2e5eae0e93fa18aee9e7bf", + "sha256:4aff049b5e629ef9b3e9e617fa6e2dfeda1bf87e01bcfecaf3949af9e210105e", + "sha256:4b38b1570242fbab8d86a84128fb5b5234a2f70c2e32f3070143a6d94bc854cf", + "sha256:4d46c7b4173415d8e583045fbc4daa48b40e31b19ce595b8d92cf639396c15d5", + "sha256:4f1c9866ccf48a6df2b06823e6ae80573529f2af3a0992ec4fe75b1a510df8a6", + "sha256:4f7acae3cf1a2a2361ec4c8e787eaaa86a94171d2417aae53c0cca6ca3118ff6", + "sha256:54d9ddea424cd19d3ff6128601a4a4d23d54a421f9b4c0fff740505813739a91", + "sha256:58718e181c56a3c02d25b09d4115eb02aafe1a732ce5714ab70326d9776457c3", + "sha256:5ede29d91a40ba22ac1b922ef510aab871652f6c88ef60b9dcdf773c6d32ad7a", + "sha256:61645818edd40cc6f455b851277a21bf420ce347baa0b86eaa41d51ef58ba23d", + "sha256:66bf9234e08fe561dccd62083bf67400bdbf1c67ba9efdc3dac03650e97c6088", + "sha256:673f988370f5954df96cc31fd99c7312a3af0a97f09e407399f61583f30da9bc", + "sha256:676f94c5480d8eefd97c0c7e3953315e4d8c2b71f3b49539beb2aa676c58272f", + "sha256:6c225286f2b13bab5987425558baa5cbdb2bc925b2998038fa028245ef421e75", + "sha256:7384d0b87d4635ec38db9263e6a3f1eb609e2e06087f0aa7f63b76833737b471", + "sha256:7e2fe37ac654032db1f3499fe56e77190282534810e2a8e833141a021faaab0e", + "sha256:7f2bfc0032a00405d4af2ba27f3c429e851d04fad1e5ceee4080a1c570476697", + "sha256:7f6b639c36734eaa80a6c152a238242bedcee9b953f23bb887e9102976343092", + "sha256:814375093edae5f1cb31e3407997cf3eacefb9010f96df10d64829362ae2df69", + "sha256:8224f98be68a84b19f48e0bdc14224b5a71339aff3a27df69989fa47d01296f3", + "sha256:898715cf566ec2869d5cb4d5fb4be408964704c46c96b4be267442d265390f32", + "sha256:8989f46f3d7ef79585e98fa991e6ded55d2f48ae56d2c9fa5e491a6e4effb589", + "sha256:8ba01ebc6175e1e6b7275c907a3a36be48a2d487549b656aa90c8a910d9f3178", + "sha256:8c5c6fa16412b35999320f5c9690c0f554392dc222c04e559217e0f9ae244b92", + "sha256:8c6a4e5e40156d72a40241a25cc226051c0a8d816610097a8e8f517aeacd59a2", + "sha256:8eaf44ccbc4e35762683078b72bf293f476561d8b68ec8a64f98cf32811c323e", + "sha256:8fb4fc029e135859f533025bc82047334e24b0d489e75513144f25408ecaf058", + "sha256:9093a81e18c45227eebe4c16124ebf3e0d893830c6aca7cc310bfca8fe59d857", + "sha256:94c4381ffba9cc508b37d2e536b418d5ea9cfdc2848b9a7fea6aebad4ec6aac1", + "sha256:94fac7c6e77ccb1ca91e9eb4cb0ac0270b9fb9b289738654120ba8cebb1189c6", + "sha256:95c4dc6f61d610bc0ee1edc6f29d993f10febfe5b76bb470b486d90bbece6b22", + "sha256:975218eee0e6d24eb336d0328c768ebc5d617609affaca5dbbd6dd1984f16ed0", + "sha256:ad146dae5977c4dd435eb31373b3fe9b0b1bf26858c6fc452bf6af394067e10b", + "sha256:afe16a84498441d05e9189a15900640a2d2b5e76cf4efe8cbb088ab4f112ee57", + "sha256:b1c43eb1ab7cbf411b8e387dc169acb31f0ca0d8c09ba63f9eac67829585b44f", + "sha256:b90078989ef3fc45cf9221d3859acd1108af7560c52397ff4ace8ad7052a132e", + "sha256:b98e698dc34966e5976e10bbca6d26d6724e6bdea853c7c10162a3235aba6e16", + "sha256:ba5a8b74c2a8af7d862399cdedce1533642fa727def0b8c3e3e02fcb52dca1b1", + "sha256:c31ad0c0c507894e3eaa843415841995bf8de4d6b2d24c6e33099f4bc9fc0d4f", + "sha256:c3b9162bab7e42f21243effc822652dc5bb5e8ff42a4eb62fe7782bcbcdfacf6", + "sha256:c58c6837a2c2a7cf3133983e64173aec11f9c2cd8e87ec2fdc16ce727bcf1a04", + "sha256:c83f7a107abb89a227d6c454c613e7606c12a42b9a4ca9c5d7dad25d47c776ae", + "sha256:cde98f323d6bf161041e7627a5fd763f9fd829bcfcd089804a5fdce7bb6e1b7d", + "sha256:ce91db90dbf37bb6fa0997f26574107e1b9d5ff939315247b7e615baa8ec313b", + "sha256:d00f3c5e0d764a5c9aa5a62d99728c56d455310bcc288a79cab10157b3af426f", + "sha256:d17920f18e6ee090bdd3d0bfffd769d9f2cb4c8ffde3eb203777a3895c128862", + "sha256:d55f011da0a843c3d3df2c2cf4e537b8070a419f891c930245f05d329c4b0689", + "sha256:d742c36ed44f2798c8d3f4bc511f479b9ceef2b93f348671184139e7d708042c", + "sha256:d9a487ef090aea982d748b1b0d74fe7c3950b109df967630a20584f9a99c0683", + "sha256:d9ef084e3dc690ad50137cc05831c52b6ca428096e6deb3c43e95827f531d5ef", + "sha256:da452c2c322e9ce0cfef392e469a26d63d42860f829026a63374fde6b5c5876f", + "sha256:dc4826823121783dccc0871e3f405417ac116055bf184ac04c36f98b75aacd12", + "sha256:de7a5299827253023c55ea549444e058c0eb496931fa05d693b95140a947cb73", + "sha256:e04a1f2a65ad2f93aa20f9ff9f1b672bf912413e5547f60749fa2ef8a644e061", + "sha256:e1ca1ef5ba129718a8fc827b0867f6aa4e893c56eb00003b7367f8a733a9b072", + "sha256:ee40b40aa753d844162dcc80d0fe256b87cba48ca0054f64e68000453caead11", + "sha256:f071854b47d39591ce9a17981c46790acb30518e2f83dfca8db2dfa091178691", + "sha256:f29930bc2921cef955ba39a3ff87d2c4398a0394ae217f41cb02d5c26c8b1b77", + "sha256:f489a2c9e6455d87eabf907ac0b7d230a9786be43fbe884ad184ddf9e9c1e385", + "sha256:f5bf3ead3cb66ab990ee2561373b009db5bc0e857549b6c9ba84b20bc462e172", + "sha256:f6f18898ace4bcd2d41a122916475344a87f1dfdec626ecde9ee802a711bc569", + "sha256:f8112fb501b1e0567a1251a2fd0747baae60a4ab325a871e975b7bb67e59221f", + "sha256:fd31f176429cecbc1ba499d4aba31aaccfea488f418d60376b911269d3b883c5" ], "markers": "python_version >= '3.8'", - "version": "==3.9.5" + "version": "==3.10.5" }, "aionotify": { "hashes": [ "sha256:25816a9eef030c774beaee22189a24e29bc43f81cebe574ef723851eaf89ddee", "sha256:9651e1373873c75786101330e302e114f85b6e8b5ad70b491497c8b3609a8449" ], + "markers": "python_version >= '3.8'", "version": "==0.3.1" }, "aiosignal": { @@ -136,27 +159,27 @@ }, "attrs": { "hashes": [ - "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", - "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1" + "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", + "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2" ], "markers": "python_version >= '3.7'", - "version": "==23.2.0" + "version": "==24.2.0" }, "cachetools": { "hashes": [ - "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945", - "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105" + "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292", + "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a" ], "markers": "python_version >= '3.7'", - "version": "==5.3.3" + "version": "==5.5.0" }, "certifi": { "hashes": [ - "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516", - "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56" + "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" ], "markers": "python_version >= '3.6'", - "version": "==2024.6.2" + "version": "==2024.8.30" }, "charset-normalizer": { "hashes": [ @@ -272,11 +295,11 @@ }, "exceptiongroup": { "hashes": [ - "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad", - "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16" + "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", + "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc" ], "markers": "python_version < '3.11'", - "version": "==1.2.1" + "version": "==1.2.2" }, "frozenlist": { "hashes": [ @@ -363,28 +386,28 @@ }, "google-api-core": { "hashes": [ - "sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125", - "sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd" + "sha256:53ec0258f2837dd53bbd3d3df50f5359281b3cc13f800c941dd15a9b5a415af4", + "sha256:ca07de7e8aa1c98a8bfca9321890ad2340ef7f2eb136e558cee68f24b94b0a8f" ], "markers": "python_version >= '3.7'", - "version": "==2.19.1" + "version": "==2.19.2" }, "google-api-python-client": { "hashes": [ - "sha256:4a8f0bea651a212997cc83c0f271fc86f80ef93d1cee9d84de7dfaeef2a858b6", - "sha256:ba05d60f6239990b7994f6328f17bb154c602d31860fb553016dc9f8ce886945" + "sha256:8b84dde11aaccadc127e4846f5cd932331d804ea324e353131595e3f25376e97", + "sha256:d74da1358f3f2d63daf3c6f26bd96d89652051183bc87cf10a56ceb2a70beb50" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==2.134.0" + "version": "==2.145.0" }, "google-auth": { "hashes": [ - "sha256:8df7da660f62757388b8a7f249df13549b3373f24388cb5d2f1dd91cc18180b5", - "sha256:ab630a1320f6720909ad76a7dbdb6841cdf5c66b328d690027e4867bdfb16688" + "sha256:72fd4733b80b6d777dcde515628a9eb4a577339437012874ea286bca7261ee65", + "sha256:8eb87396435c19b20d32abd2f984e31c191a15284af72eb922f10e5bde9c04cc" ], "markers": "python_version >= '3.7'", - "version": "==2.30.0" + "version": "==2.34.0" }, "google-auth-httplib2": { "hashes": [ @@ -395,19 +418,19 @@ }, "google-auth-oauthlib": { "hashes": [ - "sha256:292d2d3783349f2b0734a0a0207b1e1e322ac193c2c09d8f7c613fb7cc501ea8", - "sha256:297c1ce4cb13a99b5834c74a1fe03252e1e499716718b190f56bcb9c4abc4faf" + "sha256:2d58a27262d55aa1b87678c3ba7142a080098cbc2024f903c62355deb235d91f", + "sha256:afd0cad092a2eaa53cd8e8298557d6de1034c6cb4a740500b5357b648af97263" ], "markers": "python_version >= '3.6'", - "version": "==1.2.0" + "version": "==1.2.1" }, "googleapis-common-protos": { "hashes": [ - "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945", - "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87" + "sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63", + "sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0" ], "markers": "python_version >= '3.7'", - "version": "==1.63.2" + "version": "==1.65.0" }, "gspread": { "hashes": [ @@ -433,19 +456,11 @@ }, "idna": { "hashes": [ - "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", - "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" - ], - "markers": "python_version >= '3.5'", - "version": "==3.7" - }, - "joblib": { - "hashes": [ - "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", - "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e" + "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac", + "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603" ], - "markers": "python_version >= '3.8'", - "version": "==1.4.2" + "markers": "python_version >= '3.6'", + "version": "==3.8" }, "jsonschema": { "hashes": [ @@ -457,99 +472,101 @@ }, "multidict": { "hashes": [ - "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556", - "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c", - "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29", - "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b", - "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8", - "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7", - "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd", - "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40", - "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6", - "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3", - "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c", - "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9", - "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5", - "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae", - "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442", - "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9", - "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc", - "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c", - "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea", - "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5", - "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50", - "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182", - "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453", - "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e", - "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600", - "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733", - "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda", - "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241", - "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461", - "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e", - "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e", - "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b", - "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e", - "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7", - "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386", - "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd", - "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9", - "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf", - "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee", - "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5", - "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a", - "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271", - "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54", - "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4", - "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496", - "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb", - "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319", - "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3", - "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f", - "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527", - "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed", - "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604", - "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef", - "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8", - "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5", - "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5", - "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626", - "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c", - "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d", - "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c", - "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc", - "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc", - "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b", - "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38", - "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450", - "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1", - "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f", - "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3", - "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755", - "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226", - "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a", - "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046", - "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf", - "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479", - "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e", - "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1", - "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a", - "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83", - "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929", - "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93", - "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a", - "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c", - "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44", - "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89", - "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba", - "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e", - "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da", - "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24", - "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423", - "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef" + "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f", + "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056", + "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761", + "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3", + "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b", + "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6", + "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748", + "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966", + "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f", + "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1", + "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6", + "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada", + "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305", + "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2", + "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d", + "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a", + "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef", + "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c", + "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb", + "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60", + "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6", + "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4", + "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478", + "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81", + "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7", + "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56", + "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3", + "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6", + "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30", + "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb", + "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506", + "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0", + "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925", + "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c", + "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6", + "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e", + "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95", + "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2", + "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133", + "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2", + "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa", + "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3", + "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3", + "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436", + "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657", + "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581", + "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492", + "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43", + "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2", + "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2", + "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926", + "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057", + "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc", + "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80", + "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255", + "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1", + "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972", + "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53", + "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1", + "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423", + "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a", + "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160", + "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c", + "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd", + "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa", + "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5", + "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b", + "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa", + "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef", + "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44", + "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4", + "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156", + "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753", + "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28", + "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d", + "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a", + "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304", + "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008", + "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429", + "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72", + "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399", + "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3", + "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392", + "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167", + "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c", + "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774", + "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351", + "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76", + "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875", + "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd", + "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28", + "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db" ], - "markers": "python_version >= '3.7'", - "version": "==6.0.5" + "markers": "python_version >= '3.8'", + "version": "==6.1.0" }, "numpy": { "hashes": [ @@ -590,7 +607,7 @@ "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3", "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f" ], - "markers": "python_version < '3.12' and python_version >= '3.9'", + "markers": "python_version >= '3.9'", "version": "==1.26.4" }, "oauth2client": { @@ -675,14 +692,13 @@ }, "pandas-stubs": { "hashes": [ - "sha256:2dcc86e8fa6ea41535a4561c1f08b3942ba5267b464eff2e99caeee66f9e4cd1", - "sha256:e08ce7f602a4da2bff5a67475ba881c39f2a4d4f7fccc1cba57c6f35a379c6c0" + "sha256:3c0951a2c3e45e3475aed9d80b7147ae82f176b9e42e9fb321cfdebf3d411b3d", + "sha256:e230f5fa4065f9417804f4d65cd98f86c002efcc07933e8abcd48c3fad9c30a2" ], "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==2.2.2.240603" + "markers": "python_version >= '3.10'", + "version": "==2.2.2.240909" }, - "proto-plus": { "hashes": [ "sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445", @@ -693,93 +709,91 @@ }, "protobuf": { "hashes": [ - "sha256:0e341109c609749d501986b835f667c6e1e24531096cff9d34ae411595e26505", - "sha256:176c12b1f1c880bf7a76d9f7c75822b6a2bc3db2d28baa4d300e8ce4cde7409b", - "sha256:354d84fac2b0d76062e9b3221f4abbbacdfd2a4d8af36bab0474f3a0bb30ab38", - "sha256:4fadd8d83e1992eed0248bc50a4a6361dc31bcccc84388c54c86e530b7f58863", - "sha256:54330f07e4949d09614707c48b06d1a22f8ffb5763c159efd5c0928326a91470", - "sha256:610e700f02469c4a997e58e328cac6f305f649826853813177e6290416e846c6", - "sha256:7fc3add9e6003e026da5fc9e59b131b8f22b428b991ccd53e2af8071687b4fce", - "sha256:9e8f199bf7f97bd7ecebffcae45ebf9527603549b2b562df0fbc6d4d688f14ca", - "sha256:a109916aaac42bff84702fb5187f3edadbc7c97fc2c99c5ff81dd15dcce0d1e5", - "sha256:b848dbe1d57ed7c191dfc4ea64b8b004a3f9ece4bf4d0d80a367b76df20bf36e", - "sha256:f3ecdef226b9af856075f28227ff2c90ce3a594d092c39bee5513573f25e2714" + "sha256:018db9056b9d75eb93d12a9d35120f97a84d9a919bcab11ed56ad2d399d6e8dd", + "sha256:510ed78cd0980f6d3218099e874714cdf0d8a95582e7b059b06cabad855ed0a0", + "sha256:532627e8fdd825cf8767a2d2b94d77e874d5ddb0adefb04b237f7cc296748681", + "sha256:6206afcb2d90181ae8722798dcb56dc76675ab67458ac24c0dd7d75d632ac9bd", + "sha256:66c3edeedb774a3508ae70d87b3a19786445fe9a068dd3585e0cefa8a77b83d0", + "sha256:6d7cc9e60f976cf3e873acb9a40fed04afb5d224608ed5c1a105db4a3f09c5b6", + "sha256:853db610214e77ee817ecf0514e0d1d052dff7f63a0c157aa6eabae98db8a8de", + "sha256:d001a73c8bc2bf5b5c1360d59dd7573744e163b3607fa92788b7f3d5fefbd9a5", + "sha256:dde74af0fa774fa98892209992295adbfb91da3fa98c8f67a88afe8f5a349add", + "sha256:dde9fcaa24e7a9654f4baf2a55250b13a5ea701493d904c54069776b99a8216b", + "sha256:eef7a8a2f4318e2cb2dee8666d26e58eaf437c14788f3a2911d0c3da40405ae8" ], "markers": "python_version >= '3.8'", - "version": "==5.27.2" + "version": "==5.28.0" }, "pyasn1": { "hashes": [ - "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c", - "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473" + "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034" ], "markers": "python_version >= '3.8'", - "version": "==0.6.0" + "version": "==0.6.1" }, "pyasn1-modules": { "hashes": [ - "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6", - "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b" + "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c" ], "markers": "python_version >= '3.8'", - "version": "==0.4.0" + "version": "==0.4.1" }, "pydantic": { "hashes": [ - "sha256:098ad8de840c92ea586bf8efd9e2e90c6339d33ab5c1cfbb85be66e4ecf8213f", - "sha256:0e2495309b1266e81d259a570dd199916ff34f7f51f1b549a0d37a6d9b17b4dc", - "sha256:0fa51175313cc30097660b10eec8ca55ed08bfa07acbfe02f7a42f6c242e9a4b", - "sha256:11289fa895bcbc8f18704efa1d8020bb9a86314da435348f59745473eb042e6b", - "sha256:2a72d2a5ff86a3075ed81ca031eac86923d44bc5d42e719d585a8eb547bf0c9b", - "sha256:371dcf1831f87c9e217e2b6a0c66842879a14873114ebb9d0861ab22e3b5bb1e", - "sha256:409b2b36d7d7d19cd8310b97a4ce6b1755ef8bd45b9a2ec5ec2b124db0a0d8f3", - "sha256:4866a1579c0c3ca2c40575398a24d805d4db6cb353ee74df75ddeee3c657f9a7", - "sha256:48db882e48575ce4b39659558b2f9f37c25b8d348e37a2b4e32971dd5a7d6227", - "sha256:525bbef620dac93c430d5d6bdbc91bdb5521698d434adf4434a7ef6ffd5c4b7f", - "sha256:543da3c6914795b37785703ffc74ba4d660418620cc273490d42c53949eeeca6", - "sha256:62d96b8799ae3d782df7ec9615cb59fc32c32e1ed6afa1b231b0595f6516e8ab", - "sha256:6654028d1144df451e1da69a670083c27117d493f16cf83da81e1e50edce72ad", - "sha256:7017971ffa7fd7808146880aa41b266e06c1e6e12261768a28b8b41ba55c8076", - "sha256:7623b59876f49e61c2e283551cc3647616d2fbdc0b4d36d3d638aae8547ea681", - "sha256:7e17c0ee7192e54a10943f245dc79e36d9fe282418ea05b886e1c666063a7b54", - "sha256:820ae12a390c9cbb26bb44913c87fa2ff431a029a785642c1ff11fed0a095fcb", - "sha256:94833612d6fd18b57c359a127cbfd932d9150c1b72fea7c86ab58c2a77edd7c7", - "sha256:95ef534e3c22e5abbdbdd6f66b6ea9dac3ca3e34c5c632894f8625d13d084cbe", - "sha256:9c803a5113cfab7bbb912f75faa4fc1e4acff43e452c82560349fff64f852e1b", - "sha256:9e53fb834aae96e7b0dadd6e92c66e7dd9cdf08965340ed04c16813102a47fab", - "sha256:ab2f976336808fd5d539fdc26eb51f9aafc1f4b638e212ef6b6f05e753c8011d", - "sha256:ad1e33dc6b9787a6f0f3fd132859aa75626528b49cc1f9e429cdacb2608ad5f0", - "sha256:ae5184e99a060a5c80010a2d53c99aee76a3b0ad683d493e5f0620b5d86eeb75", - "sha256:aeb4e741782e236ee7dc1fb11ad94dc56aabaf02d21df0e79e0c21fe07c95741", - "sha256:b4ad32aed3bf5eea5ca5decc3d1bbc3d0ec5d4fbcd72a03cdad849458decbc63", - "sha256:b8ad363330557beac73159acfbeed220d5f1bfcd6b930302a987a375e02f74fd", - "sha256:bfbb18b616abc4df70591b8c1ff1b3eabd234ddcddb86b7cac82657ab9017e33", - "sha256:c1e51d1af306641b7d1574d6d3307eaa10a4991542ca324f0feb134fee259815", - "sha256:c31d281c7485223caf6474fc2b7cf21456289dbaa31401844069b77160cab9c7", - "sha256:c7e8988bb16988890c985bd2093df9dd731bfb9d5e0860db054c23034fab8f7a", - "sha256:c87cedb4680d1614f1d59d13fea353faf3afd41ba5c906a266f3f2e8c245d655", - "sha256:cafb9c938f61d1b182dfc7d44a7021326547b7b9cf695db5b68ec7b590214773", - "sha256:d2f89a719411cb234105735a520b7c077158a81e0fe1cb05a79c01fc5eb59d3c", - "sha256:d4b40c9e13a0b61583e5599e7950490c700297b4a375b55b2b592774332798b7", - "sha256:d4ecb515fa7cb0e46e163ecd9d52f9147ba57bc3633dca0e586cdb7a232db9e3", - "sha256:d8c209af63ccd7b22fba94b9024e8b7fd07feffee0001efae50dd99316b27768", - "sha256:db3b48d9283d80a314f7a682f7acae8422386de659fffaba454b77a083c3937d", - "sha256:e41b5b973e5c64f674b3b4720286ded184dcc26a691dd55f34391c62c6934688", - "sha256:e840e6b2026920fc3f250ea8ebfdedf6ea7a25b77bf04c6576178e681942ae0f", - "sha256:ebb249096d873593e014535ab07145498957091aa6ae92759a32d40cb9998e2e", - "sha256:f434160fb14b353caf634149baaf847206406471ba70e64657c1e8330277a991", - "sha256:fa43f362b46741df8f201bf3e7dff3569fa92069bcc7b4a740dea3602e27ab7a" + "sha256:069b9c9fc645474d5ea3653788b544a9e0ccd3dca3ad8c900c4c6eac844b4620", + "sha256:06a189b81ffc52746ec9c8c007f16e5167c8b0a696e1a726369327e3db7b2a82", + "sha256:11d9d9b87b50338b1b7de4ebf34fd29fdb0d219dc07ade29effc74d3d2609c62", + "sha256:15fdbe568beaca9aacfccd5ceadfb5f1a235087a127e8af5e48df9d8a45ae85c", + "sha256:19a3bd00b9dafc2cd7250d94d5b578edf7a0bd7daf102617153ff9a8fa37871c", + "sha256:23e8ec1ce4e57b4f441fc91e3c12adba023fedd06868445a5b5f1d48f0ab3682", + "sha256:24a4a159d0f7a8e26bf6463b0d3d60871d6a52eac5bb6a07a7df85c806f4c048", + "sha256:2ce3fcf75b2bae99aa31bd4968de0474ebe8c8258a0110903478bd83dfee4e3b", + "sha256:335a32d72c51a313b33fa3a9b0fe283503272ef6467910338e123f90925f0f03", + "sha256:3445426da503c7e40baccefb2b2989a0c5ce6b163679dd75f55493b460f05a8f", + "sha256:34a3613c7edb8c6fa578e58e9abe3c0f5e7430e0fc34a65a415a1683b9c32d9a", + "sha256:3d5492dbf953d7d849751917e3b2433fb26010d977aa7a0765c37425a4026ff1", + "sha256:44ae8a3e35a54d2e8fa88ed65e1b08967a9ef8c320819a969bfa09ce5528fafe", + "sha256:467a14ee2183bc9c902579bb2f04c3d3dac00eff52e252850509a562255b2a33", + "sha256:46f379b8cb8a3585e3f61bf9ae7d606c70d133943f339d38b76e041ec234953f", + "sha256:49e26c51ca854286bffc22b69787a8d4063a62bf7d83dc21d44d2ff426108518", + "sha256:65f7361a09b07915a98efd17fdec23103307a54db2000bb92095457ca758d485", + "sha256:6951f3f47cb5ca4da536ab161ac0163cab31417d20c54c6de5ddcab8bc813c3f", + "sha256:72fa46abace0a7743cc697dbb830a41ee84c9db8456e8d77a46d79b537efd7ec", + "sha256:74fe19dda960b193b0eb82c1f4d2c8e5e26918d9cda858cbf3f41dd28549cb70", + "sha256:7a4c5eec138a9b52c67f664c7d51d4c7234c5ad65dd8aacd919fb47445a62c86", + "sha256:80b982d42515632eb51f60fa1d217dfe0729f008e81a82d1544cc392e0a50ddf", + "sha256:941a2eb0a1509bd7f31e355912eb33b698eb0051730b2eaf9e70e2e1589cae1d", + "sha256:9f463abafdc92635da4b38807f5b9972276be7c8c5121989768549fceb8d2588", + "sha256:a00e63104346145389b8e8f500bc6a241e729feaf0559b88b8aa513dd2065481", + "sha256:aad8771ec8dbf9139b01b56f66386537c6fe4e76c8f7a47c10261b69ad25c2c9", + "sha256:ae6fa2008e1443c46b7b3a5eb03800121868d5ab6bc7cda20b5df3e133cde8b3", + "sha256:b661ce52c7b5e5f600c0c3c5839e71918346af2ef20062705ae76b5c16914cab", + "sha256:b74be007703547dc52e3c37344d130a7bfacca7df112a9e5ceeb840a9ce195c7", + "sha256:baebdff1907d1d96a139c25136a9bb7d17e118f133a76a2ef3b845e831e3403a", + "sha256:c20f682defc9ef81cd7eaa485879ab29a86a0ba58acf669a78ed868e72bb89e0", + "sha256:c3e742f62198c9eb9201781fbebe64533a3bbf6a76a91b8d438d62b813079dbc", + "sha256:c5ae6b7c8483b1e0bf59e5f1843e4fd8fd405e11df7de217ee65b98eb5462861", + "sha256:c6d0a9f9eccaf7f438671a64acf654ef0d045466e63f9f68a579e2383b63f357", + "sha256:cbfbca662ed3729204090c4d09ee4beeecc1a7ecba5a159a94b5a4eb24e3759a", + "sha256:d5389eb3b48a72da28c6e061a247ab224381435256eb541e175798483368fdd3", + "sha256:e306e280ebebc65040034bff1a0a81fd86b2f4f05daac0131f29541cafd80b80", + "sha256:e405ffcc1254d76bb0e760db101ee8916b620893e6edfbfee563b3c6f7a67c02", + "sha256:e9ee4e6ca1d9616797fa2e9c0bfb8815912c7d67aca96f77428e316741082a1b", + "sha256:ef0fe7ad7cbdb5f372463d42e6ed4ca9c443a52ce544472d8842a0576d830da5", + "sha256:efbc8a7f9cb5fe26122acba1852d8dcd1e125e723727c59dcd244da7bdaa54f2", + "sha256:fcb20d4cb355195c75000a49bb4a31d75e4295200df620f454bbc6bdf60ca890", + "sha256:fe734914977eed33033b70bfc097e1baaffb589517863955430bf2e0846ac30f" ], "markers": "python_version >= '3.7'", - "version": "==1.10.17" + "version": "==1.10.18" }, "pyparsing": { "hashes": [ - "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad", - "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742" + "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c", + "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032" ], "markers": "python_version >= '3.1'", - "version": "==3.1.2" + "version": "==3.1.4" }, "pyrsistent": { "hashes": [ @@ -844,10 +858,18 @@ }, "pytz": { "hashes": [ - "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812", - "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319" + "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", + "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725" ], - "version": "==2024.1" + "version": "==2024.2" + }, + "pyusb": { + "hashes": [ + "sha256:2b4c7cb86dbadf044dfb9d3a4ff69fd217013dbe78a792177a3feb172449ea36", + "sha256:a4cc7404a203144754164b8b40994e2849fde1cfff06b08492f12fff9d9de7b9" + ], + "markers": "python_full_version >= '3.6.0'", + "version": "==1.2.1" }, "pywin32": { "hashes": [ @@ -893,72 +915,13 @@ "markers": "python_version >= '3.6' and python_version < '4'", "version": "==4.9" }, - "scikit-learn": { - "hashes": [ - "sha256:057b991ac64b3e75c9c04b5f9395eaf19a6179244c089afdebaad98264bff37c", - "sha256:118a8d229a41158c9f90093e46b3737120a165181a1b58c03461447aa4657415", - "sha256:12e40ac48555e6b551f0a0a5743cc94cc5a765c9513fe708e01f0aa001da2801", - "sha256:174beb56e3e881c90424e21f576fa69c4ffcf5174632a79ab4461c4c960315ac", - "sha256:1b94d6440603752b27842eda97f6395f570941857456c606eb1d638efdb38184", - "sha256:1f77547165c00625551e5c250cefa3f03f2fc92c5e18668abd90bfc4be2e0bff", - "sha256:261fe334ca48f09ed64b8fae13f9b46cc43ac5f580c4a605cbb0a517456c8f71", - "sha256:2a65af2d8a6cce4e163a7951a4cfbfa7fceb2d5c013a4b593686c7f16445cf9d", - "sha256:2c75ea812cd83b1385bbfa94ae971f0d80adb338a9523f6bbcb5e0b0381151d4", - "sha256:40fb7d4a9a2db07e6e0cae4dc7bdbb8fada17043bac24104d8165e10e4cff1a2", - "sha256:460806030c666addee1f074788b3978329a5bfdc9b7d63e7aad3f6d45c67a210", - "sha256:47132440050b1c5beb95f8ba0b2402bbd9057ce96ec0ba86f2f445dd4f34df67", - "sha256:4c0c56c3005f2ec1db3787aeaabefa96256580678cec783986836fc64f8ff622", - "sha256:789e3db01c750ed6d496fa2db7d50637857b451e57bcae863bff707c1247bef7", - "sha256:855fc5fa8ed9e4f08291203af3d3e5fbdc4737bd617a371559aaa2088166046e", - "sha256:a03b09f9f7f09ffe8c5efffe2e9de1196c696d811be6798ad5eddf323c6f4d40", - "sha256:a3a10e1d9e834e84d05e468ec501a356226338778769317ee0b84043c0d8fb06", - "sha256:a90c5da84829a0b9b4bf00daf62754b2be741e66b5946911f5bdfaa869fcedd6", - "sha256:d82c2e573f0f2f2f0be897e7a31fcf4e73869247738ab8c3ce7245549af58ab8", - "sha256:df8ccabbf583315f13160a4bb06037bde99ea7d8211a69787a6b7c5d4ebb6fc3", - "sha256:f405c4dae288f5f6553b10c4ac9ea7754d5180ec11e296464adb5d6ac68b6ef5" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==1.5.0" - }, - "scipy": { - "hashes": [ - "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d", - "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c", - "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca", - "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9", - "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54", - "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16", - "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2", - "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5", - "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59", - "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326", - "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b", - "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1", - "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d", - "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24", - "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627", - "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c", - "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa", - "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949", - "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989", - "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004", - "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f", - "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884", - "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299", - "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94", - "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f" - ], - "markers": "python_version >= '3.9'", - "version": "==1.13.1" - }, "setuptools": { "hashes": [ - "sha256:937a48c7cdb7a21eb53cd7f9b59e525503aa8abaf3584c730dc5f7a5bec3a650", - "sha256:a58a8fde0541dab0419750bcc521fbdf8585f6e5cb41909df3a472ef7b81ca95" + "sha256:5f4c08aa4d3ebcb57a50c33b1b07e94315d7fc7230f7115e47fc99776c8ce308", + "sha256:95b40ed940a1c67eb70fc099094bd6e99c6ee7c23aa2306f4d2697ba7916f9c6" ], "markers": "python_version >= '3.8'", - "version": "==70.1.1" + "version": "==74.1.2" }, "six": { "hashes": [ @@ -970,12 +933,12 @@ }, "slack-sdk": { "hashes": [ - "sha256:001a4013698d3f244645add49c80adf8addc3a6bf633193848f7cbae3d387e0b", - "sha256:42d1c95f7159887ddb4841d461fbe7ab0c48e4968f3cd44eaaa792cf109f4425" + "sha256:af8fc4ef1d1cbcecd28d01acf6955a3bb5b13d56f0a43a1b1c7e3b212cc5ec5b", + "sha256:f35e85f2847e6c25cf7c2d1df206ca0ad75556263fb592457bf03cca68ef64bb" ], "index": "pypi", "markers": "python_version >= '3.6'", - "version": "==3.30.0" + "version": "==3.32.0" }, "slackclient": { "hashes": [ @@ -1001,14 +964,6 @@ ], "version": "==0.4.15" }, - "threadpoolctl": { - "hashes": [ - "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107", - "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467" - ], - "markers": "python_version >= '3.8'", - "version": "==3.5.0" - }, "types-httplib2": { "hashes": [ "sha256:1eda99fea18ec8a1dc1a725ead35b889d0836fec1b11ae6f1fe05440724c1d15", @@ -1136,99 +1091,101 @@ }, "yarl": { "hashes": [ - "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51", - "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce", - "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559", - "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0", - "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81", - "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc", - "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4", - "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c", - "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130", - "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136", - "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e", - "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec", - "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7", - "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1", - "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455", - "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099", - "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129", - "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10", - "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142", - "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98", - "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa", - "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7", - "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525", - "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c", - "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9", - "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c", - "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8", - "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b", - "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf", - "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23", - "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd", - "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27", - "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f", - "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece", - "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434", - "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec", - "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff", - "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78", - "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d", - "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863", - "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53", - "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31", - "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15", - "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5", - "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b", - "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57", - "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3", - "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1", - "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f", - "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad", - "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c", - "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7", - "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2", - "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b", - "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2", - "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b", - "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9", - "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be", - "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e", - "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984", - "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4", - "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074", - "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2", - "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392", - "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91", - "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541", - "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf", - "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572", - "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66", - "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575", - "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14", - "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5", - "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1", - "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e", - "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551", - "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17", - "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead", - "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0", - "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe", - "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234", - "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0", - "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7", - "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34", - "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42", - "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385", - "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78", - "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be", - "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958", - "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749", - "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec" + "sha256:01a8697ec24f17c349c4f655763c4db70eebc56a5f82995e5e26e837c6eb0e49", + "sha256:02da8759b47d964f9173c8675710720b468aa1c1693be0c9c64abb9d8d9a4867", + "sha256:04293941646647b3bfb1719d1d11ff1028e9c30199509a844da3c0f5919dc520", + "sha256:067b961853c8e62725ff2893226fef3d0da060656a9827f3f520fb1d19b2b68a", + "sha256:077da604852be488c9a05a524068cdae1e972b7dc02438161c32420fb4ec5e14", + "sha256:09696438cb43ea6f9492ef237761b043f9179f455f405279e609f2bc9100212a", + "sha256:0b8486f322d8f6a38539136a22c55f94d269addb24db5cb6f61adc61eabc9d93", + "sha256:0ea9682124fc062e3d931c6911934a678cb28453f957ddccf51f568c2f2b5e05", + "sha256:0f351fa31234699d6084ff98283cb1e852270fe9e250a3b3bf7804eb493bd937", + "sha256:14438dfc5015661f75f85bc5adad0743678eefee266ff0c9a8e32969d5d69f74", + "sha256:15061ce6584ece023457fb8b7a7a69ec40bf7114d781a8c4f5dcd68e28b5c53b", + "sha256:15439f3c5c72686b6c3ff235279630d08936ace67d0fe5c8d5bbc3ef06f5a420", + "sha256:17b5a386d0d36fb828e2fb3ef08c8829c1ebf977eef88e5367d1c8c94b454639", + "sha256:18ac56c9dd70941ecad42b5a906820824ca72ff84ad6fa18db33c2537ae2e089", + "sha256:1bb2d9e212fb7449b8fb73bc461b51eaa17cc8430b4a87d87be7b25052d92f53", + "sha256:1e969fa4c1e0b1a391f3fcbcb9ec31e84440253325b534519be0d28f4b6b533e", + "sha256:1fa2e7a406fbd45b61b4433e3aa254a2c3e14c4b3186f6e952d08a730807fa0c", + "sha256:2164cd9725092761fed26f299e3f276bb4b537ca58e6ff6b252eae9631b5c96e", + "sha256:21a7c12321436b066c11ec19c7e3cb9aec18884fe0d5b25d03d756a9e654edfe", + "sha256:238a21849dd7554cb4d25a14ffbfa0ef380bb7ba201f45b144a14454a72ffa5a", + "sha256:250e888fa62d73e721f3041e3a9abf427788a1934b426b45e1b92f62c1f68366", + "sha256:25861303e0be76b60fddc1250ec5986c42f0a5c0c50ff57cc30b1be199c00e63", + "sha256:267b24f891e74eccbdff42241c5fb4f974de2d6271dcc7d7e0c9ae1079a560d9", + "sha256:27fcb271a41b746bd0e2a92182df507e1c204759f460ff784ca614e12dd85145", + "sha256:2909fa3a7d249ef64eeb2faa04b7957e34fefb6ec9966506312349ed8a7e77bf", + "sha256:3257978c870728a52dcce8c2902bf01f6c53b65094b457bf87b2644ee6238ddc", + "sha256:327c724b01b8641a1bf1ab3b232fb638706e50f76c0b5bf16051ab65c868fac5", + "sha256:3de5292f9f0ee285e6bd168b2a77b2a00d74cbcfa420ed078456d3023d2f6dff", + "sha256:3fce4da3703ee6048ad4138fe74619c50874afe98b1ad87b2698ef95bf92c96d", + "sha256:3ff6b1617aa39279fe18a76c8d165469c48b159931d9b48239065767ee455b2b", + "sha256:400cd42185f92de559d29eeb529e71d80dfbd2f45c36844914a4a34297ca6f00", + "sha256:4179522dc0305c3fc9782549175c8e8849252fefeb077c92a73889ccbcd508ad", + "sha256:4307d9a3417eea87715c9736d050c83e8c1904e9b7aada6ce61b46361b733d92", + "sha256:476e20c433b356e16e9a141449f25161e6b69984fb4cdbd7cd4bd54c17844998", + "sha256:489fa8bde4f1244ad6c5f6d11bb33e09cf0d1d0367edb197619c3e3fc06f3d91", + "sha256:48a28bed68ab8fb7e380775f0029a079f08a17799cb3387a65d14ace16c12e2b", + "sha256:48dfd117ab93f0129084577a07287376cc69c08138694396f305636e229caa1a", + "sha256:4973eac1e2ff63cf187073cd4e1f1148dcd119314ab79b88e1b3fad74a18c9d5", + "sha256:498442e3af2a860a663baa14fbf23fb04b0dd758039c0e7c8f91cb9279799bff", + "sha256:501c503eed2bb306638ccb60c174f856cc3246c861829ff40eaa80e2f0330367", + "sha256:504cf0d4c5e4579a51261d6091267f9fd997ef58558c4ffa7a3e1460bd2336fa", + "sha256:61a5f2c14d0a1adfdd82258f756b23a550c13ba4c86c84106be4c111a3a4e413", + "sha256:637c7ddb585a62d4469f843dac221f23eec3cbad31693b23abbc2c366ad41ff4", + "sha256:66b63c504d2ca43bf7221a1f72fbe981ff56ecb39004c70a94485d13e37ebf45", + "sha256:67459cf8cf31da0e2cbdb4b040507e535d25cfbb1604ca76396a3a66b8ba37a6", + "sha256:688654f8507464745ab563b041d1fb7dab5d9912ca6b06e61d1c4708366832f5", + "sha256:6907daa4b9d7a688063ed098c472f96e8181733c525e03e866fb5db480a424df", + "sha256:69721b8effdb588cb055cc22f7c5105ca6fdaa5aeb3ea09021d517882c4a904c", + "sha256:6d23754b9939cbab02c63434776df1170e43b09c6a517585c7ce2b3d449b7318", + "sha256:7175a87ab8f7fbde37160a15e58e138ba3b2b0e05492d7351314a250d61b1591", + "sha256:72bf26f66456baa0584eff63e44545c9f0eaed9b73cb6601b647c91f14c11f38", + "sha256:74db2ef03b442276d25951749a803ddb6e270d02dda1d1c556f6ae595a0d76a8", + "sha256:750f656832d7d3cb0c76be137ee79405cc17e792f31e0a01eee390e383b2936e", + "sha256:75e0ae31fb5ccab6eda09ba1494e87eb226dcbd2372dae96b87800e1dcc98804", + "sha256:768ecc550096b028754ea28bf90fde071c379c62c43afa574edc6f33ee5daaec", + "sha256:7d51324a04fc4b0e097ff8a153e9276c2593106a811704025bbc1d6916f45ca6", + "sha256:7e975a2211952a8a083d1b9d9ba26472981ae338e720b419eb50535de3c02870", + "sha256:8215f6f21394d1f46e222abeb06316e77ef328d628f593502d8fc2a9117bde83", + "sha256:8258c86f47e080a258993eed877d579c71da7bda26af86ce6c2d2d072c11320d", + "sha256:8418c053aeb236b20b0ab8fa6bacfc2feaaf7d4683dd96528610989c99723d5f", + "sha256:87f020d010ba80a247c4abc335fc13421037800ca20b42af5ae40e5fd75e7909", + "sha256:884eab2ce97cbaf89f264372eae58388862c33c4f551c15680dd80f53c89a269", + "sha256:8a336eaa7ee7e87cdece3cedb395c9657d227bfceb6781295cf56abcd3386a26", + "sha256:8aef1b64da41d18026632d99a06b3fefe1d08e85dd81d849fa7c96301ed22f1b", + "sha256:8aef97ba1dd2138112890ef848e17d8526fe80b21f743b4ee65947ea184f07a2", + "sha256:8ed653638ef669e0efc6fe2acb792275cb419bf9cb5c5049399f3556995f23c7", + "sha256:9361628f28f48dcf8b2f528420d4d68102f593f9c2e592bfc842f5fb337e44fd", + "sha256:946eedc12895873891aaceb39bceb484b4977f70373e0122da483f6c38faaa68", + "sha256:94d0caaa912bfcdc702a4204cd5e2bb01eb917fc4f5ea2315aa23962549561b0", + "sha256:964a428132227edff96d6f3cf261573cb0f1a60c9a764ce28cda9525f18f7786", + "sha256:999bfee0a5b7385a0af5ffb606393509cfde70ecca4f01c36985be6d33e336da", + "sha256:a08ea567c16f140af8ddc7cb58e27e9138a1386e3e6e53982abaa6f2377b38cc", + "sha256:a28b70c9e2213de425d9cba5ab2e7f7a1c8ca23a99c4b5159bf77b9c31251447", + "sha256:a34e1e30f1774fa35d37202bbeae62423e9a79d78d0874e5556a593479fdf239", + "sha256:a4264515f9117be204935cd230fb2a052dd3792789cc94c101c535d349b3dab0", + "sha256:a7915ea49b0c113641dc4d9338efa9bd66b6a9a485ffe75b9907e8573ca94b84", + "sha256:aac44097d838dda26526cffb63bdd8737a2dbdf5f2c68efb72ad83aec6673c7e", + "sha256:b91044952da03b6f95fdba398d7993dd983b64d3c31c358a4c89e3c19b6f7aef", + "sha256:ba444bdd4caa2a94456ef67a2f383710928820dd0117aae6650a4d17029fa25e", + "sha256:c2dc4250fe94d8cd864d66018f8344d4af50e3758e9d725e94fecfa27588ff82", + "sha256:c35f493b867912f6fda721a59cc7c4766d382040bdf1ddaeeaa7fa4d072f4675", + "sha256:c92261eb2ad367629dc437536463dc934030c9e7caca861cc51990fe6c565f26", + "sha256:ce928c9c6409c79e10f39604a7e214b3cb69552952fbda8d836c052832e6a979", + "sha256:d95b52fbef190ca87d8c42f49e314eace4fc52070f3dfa5f87a6594b0c1c6e46", + "sha256:dae7bd0daeb33aa3e79e72877d3d51052e8b19c9025ecf0374f542ea8ec120e4", + "sha256:e286580b6511aac7c3268a78cdb861ec739d3e5a2a53b4809faef6b49778eaff", + "sha256:e4b53f73077e839b3f89c992223f15b1d2ab314bdbdf502afdc7bb18e95eae27", + "sha256:e8f63904df26d1a66aabc141bfd258bf738b9bc7bc6bdef22713b4f5ef789a4c", + "sha256:f3a6d90cab0bdf07df8f176eae3a07127daafcf7457b997b2bf46776da2c7eb7", + "sha256:f41fa79114a1d2eddb5eea7b912d6160508f57440bd302ce96eaa384914cd265", + "sha256:f46f81501160c28d0c0b7333b4f7be8983dbbc161983b6fb814024d1b4952f79", + "sha256:f61db3b7e870914dbd9434b560075e0366771eecbe6d2b5561f5bc7485f39efd" ], - "markers": "python_version >= '3.7'", - "version": "==1.9.4" + "markers": "python_version >= '3.8'", + "version": "==1.11.1" } }, "develop": { @@ -1241,11 +1198,11 @@ }, "attrs": { "hashes": [ - "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", - "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1" + "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", + "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2" ], "markers": "python_version >= '3.7'", - "version": "==23.2.0" + "version": "==24.2.0" }, "black": { "hashes": [ @@ -1279,19 +1236,19 @@ }, "cachetools": { "hashes": [ - "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945", - "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105" + "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292", + "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a" ], "markers": "python_version >= '3.7'", - "version": "==5.3.3" + "version": "==5.5.0" }, "certifi": { "hashes": [ - "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516", - "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56" + "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" ], "markers": "python_version >= '3.6'", - "version": "==2024.6.2" + "version": "==2024.8.30" }, "charset-normalizer": { "hashes": [ @@ -1407,61 +1364,81 @@ }, "coverage": { "hashes": [ - "sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f", - "sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d", - "sha256:042183de01f8b6d531e10c197f7f0315a61e8d805ab29c5f7b51a01d62782747", - "sha256:1014fbf665fef86cdfd6cb5b7371496ce35e4d2a00cda501cf9f5b9e6fced69f", - "sha256:1137f46adb28e3813dec8c01fefadcb8c614f33576f672962e323b5128d9a68d", - "sha256:16852febd96acd953b0d55fc842ce2dac1710f26729b31c80b940b9afcd9896f", - "sha256:2174e7c23e0a454ffe12267a10732c273243b4f2d50d07544a91198f05c48f47", - "sha256:2214ee920787d85db1b6a0bd9da5f8503ccc8fcd5814d90796c2f2493a2f4d2e", - "sha256:3257fdd8e574805f27bb5342b77bc65578e98cbc004a92232106344053f319ba", - "sha256:3684bc2ff328f935981847082ba4fdc950d58906a40eafa93510d1b54c08a66c", - "sha256:3a6612c99081d8d6134005b1354191e103ec9705d7ba2754e848211ac8cacc6b", - "sha256:3d7564cc09dd91b5a6001754a5b3c6ecc4aba6323baf33a12bd751036c998be4", - "sha256:44da56a2589b684813f86d07597fdf8a9c6ce77f58976727329272f5a01f99f7", - "sha256:5013ed890dc917cef2c9f765c4c6a8ae9df983cd60dbb635df8ed9f4ebc9f555", - "sha256:54317c2b806354cbb2dc7ac27e2b93f97096912cc16b18289c5d4e44fc663233", - "sha256:56b4eafa21c6c175b3ede004ca12c653a88b6f922494b023aeb1e836df953ace", - "sha256:581ea96f92bf71a5ec0974001f900db495488434a6928a2ca7f01eee20c23805", - "sha256:5cd64adedf3be66f8ccee418473c2916492d53cbafbfcff851cbec5a8454b136", - "sha256:5df54843b88901fdc2f598ac06737f03d71168fd1175728054c8f5a2739ac3e4", - "sha256:65e528e2e921ba8fd67d9055e6b9f9e34b21ebd6768ae1c1723f4ea6ace1234d", - "sha256:6aae5cce399a0f065da65c7bb1e8abd5c7a3043da9dceb429ebe1b289bc07806", - "sha256:6cfb5a4f556bb51aba274588200a46e4dd6b505fb1a5f8c5ae408222eb416f99", - "sha256:7076b4b3a5f6d2b5d7f1185fde25b1e54eb66e647a1dfef0e2c2bfaf9b4c88c8", - "sha256:73ca8fbc5bc622e54627314c1a6f1dfdd8db69788f3443e752c215f29fa87a0b", - "sha256:79b356f3dd5b26f3ad23b35c75dbdaf1f9e2450b6bcefc6d0825ea0aa3f86ca5", - "sha256:7a892be37ca35eb5019ec85402c3371b0f7cda5ab5056023a7f13da0961e60da", - "sha256:8192794d120167e2a64721d88dbd688584675e86e15d0569599257566dec9bf0", - "sha256:820bc841faa502e727a48311948e0461132a9c8baa42f6b2b84a29ced24cc078", - "sha256:8f894208794b164e6bd4bba61fc98bf6b06be4d390cf2daacfa6eca0a6d2bb4f", - "sha256:a04e990a2a41740b02d6182b498ee9796cf60eefe40cf859b016650147908029", - "sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353", - "sha256:a6bb74ed465d5fb204b2ec41d79bcd28afccf817de721e8a807d5141c3426638", - "sha256:ab73b35e8d109bffbda9a3e91c64e29fe26e03e49addf5b43d85fc426dde11f9", - "sha256:aea072a941b033813f5e4814541fc265a5c12ed9720daef11ca516aeacd3bd7f", - "sha256:b1ccf5e728ccf83acd313c89f07c22d70d6c375a9c6f339233dcf792094bcbf7", - "sha256:b385d49609f8e9efc885790a5a0e89f2e3ae042cdf12958b6034cc442de428d3", - "sha256:b3d45ff86efb129c599a3b287ae2e44c1e281ae0f9a9bad0edc202179bcc3a2e", - "sha256:b4a474f799456e0eb46d78ab07303286a84a3140e9700b9e154cfebc8f527016", - "sha256:b95c3a8cb0463ba9f77383d0fa8c9194cf91f64445a63fc26fb2327e1e1eb088", - "sha256:c5986ee7ea0795a4095ac4d113cbb3448601efca7f158ec7f7087a6c705304e4", - "sha256:cdd31315fc20868c194130de9ee6bfd99755cc9565edff98ecc12585b90be882", - "sha256:cef4649ec906ea7ea5e9e796e68b987f83fa9a718514fe147f538cfeda76d7a7", - "sha256:d05c16cf4b4c2fc880cb12ba4c9b526e9e5d5bb1d81313d4d732a5b9fe2b9d53", - "sha256:d2e344d6adc8ef81c5a233d3a57b3c7d5181f40e79e05e1c143da143ccb6377d", - "sha256:d45d3cbd94159c468b9b8c5a556e3f6b81a8d1af2a92b77320e887c3e7a5d080", - "sha256:db14f552ac38f10758ad14dd7b983dbab424e731588d300c7db25b6f89e335b5", - "sha256:dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d", - "sha256:ddbd2f9713a79e8e7242d7c51f1929611e991d855f414ca9996c20e44a895f7c", - "sha256:e16f3d6b491c48c5ae726308e6ab1e18ee830b4cdd6913f2d7f77354b33f91c8", - "sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633", - "sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9", - "sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c" + "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca", + "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d", + "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6", + "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989", + "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c", + "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b", + "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223", + "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f", + "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56", + "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3", + "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8", + "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb", + "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388", + "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0", + "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a", + "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8", + "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f", + "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a", + "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962", + "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8", + "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391", + "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc", + "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2", + "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155", + "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb", + "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0", + "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c", + "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a", + "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004", + "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060", + "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232", + "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93", + "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129", + "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163", + "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de", + "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6", + "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23", + "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569", + "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", + "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778", + "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d", + "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36", + "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a", + "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6", + "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34", + "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704", + "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106", + "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9", + "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862", + "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b", + "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255", + "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16", + "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3", + "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133", + "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", + "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657", + "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d", + "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca", + "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36", + "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c", + "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e", + "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff", + "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7", + "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", + "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02", + "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c", + "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df", + "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3", + "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a", + "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959", + "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234", + "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc" ], "markers": "python_version >= '3.8'", - "version": "==7.5.4" + "version": "==7.6.1" }, "flake8": { "hashes": [ @@ -1500,37 +1477,37 @@ }, "google-api-core": { "hashes": [ - "sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125", - "sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd" + "sha256:53ec0258f2837dd53bbd3d3df50f5359281b3cc13f800c941dd15a9b5a415af4", + "sha256:ca07de7e8aa1c98a8bfca9321890ad2340ef7f2eb136e558cee68f24b94b0a8f" ], "markers": "python_version >= '3.7'", - "version": "==2.19.1" + "version": "==2.19.2" }, "google-api-python-client": { "hashes": [ - "sha256:4a8f0bea651a212997cc83c0f271fc86f80ef93d1cee9d84de7dfaeef2a858b6", - "sha256:ba05d60f6239990b7994f6328f17bb154c602d31860fb553016dc9f8ce886945" + "sha256:8b84dde11aaccadc127e4846f5cd932331d804ea324e353131595e3f25376e97", + "sha256:d74da1358f3f2d63daf3c6f26bd96d89652051183bc87cf10a56ceb2a70beb50" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==2.134.0" + "version": "==2.145.0" }, "google-api-python-client-stubs": { "hashes": [ - "sha256:0614b0cef5beac43e6ab02418f07e64ee66dc99ae4e377d54a155ac261533987", - "sha256:f3b38b46f7b5cf4f6e7cc63ca554a2d23096d49c841f38b9ea553a5237074b56" + "sha256:148e16613e070969727f39691e23a73cdb87c65a4fc8133abd4c41d17b80b313", + "sha256:3c1f9f2a7cac8d1e9a7e84ed24e6c29cf4c643b0f94e39ed09ac1b7e91ab239a" ], "index": "pypi", "markers": "python_version >= '3.7' and python_version < '4.0'", - "version": "==1.26.0" + "version": "==1.27.0" }, "google-auth": { "hashes": [ - "sha256:8df7da660f62757388b8a7f249df13549b3373f24388cb5d2f1dd91cc18180b5", - "sha256:ab630a1320f6720909ad76a7dbdb6841cdf5c66b328d690027e4867bdfb16688" + "sha256:72fd4733b80b6d777dcde515628a9eb4a577339437012874ea286bca7261ee65", + "sha256:8eb87396435c19b20d32abd2f984e31c191a15284af72eb922f10e5bde9c04cc" ], "markers": "python_version >= '3.7'", - "version": "==2.30.0" + "version": "==2.34.0" }, "google-auth-httplib2": { "hashes": [ @@ -1541,11 +1518,11 @@ }, "googleapis-common-protos": { "hashes": [ - "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945", - "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87" + "sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63", + "sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0" ], "markers": "python_version >= '3.7'", - "version": "==1.63.2" + "version": "==1.65.0" }, "httplib2": { "hashes": [ @@ -1558,11 +1535,11 @@ }, "idna": { "hashes": [ - "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", - "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" + "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac", + "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603" ], - "markers": "python_version >= '3.5'", - "version": "==3.7" + "markers": "python_version >= '3.6'", + "version": "==3.8" }, "iniconfig": { "hashes": [ @@ -1639,11 +1616,11 @@ }, "platformdirs": { "hashes": [ - "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee", - "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3" + "sha256:9e5e27a08aa095dd127b9f2e764d74254f482fef22b0970773bfba79d091ab8c", + "sha256:eb1c8582560b34ed4ba105009a4badf7f6f85768b30126f351328507b2beb617" ], "markers": "python_version >= '3.8'", - "version": "==4.2.2" + "version": "==4.3.2" }, "pluggy": { "hashes": [ @@ -1663,20 +1640,20 @@ }, "protobuf": { "hashes": [ - "sha256:0e341109c609749d501986b835f667c6e1e24531096cff9d34ae411595e26505", - "sha256:176c12b1f1c880bf7a76d9f7c75822b6a2bc3db2d28baa4d300e8ce4cde7409b", - "sha256:354d84fac2b0d76062e9b3221f4abbbacdfd2a4d8af36bab0474f3a0bb30ab38", - "sha256:4fadd8d83e1992eed0248bc50a4a6361dc31bcccc84388c54c86e530b7f58863", - "sha256:54330f07e4949d09614707c48b06d1a22f8ffb5763c159efd5c0928326a91470", - "sha256:610e700f02469c4a997e58e328cac6f305f649826853813177e6290416e846c6", - "sha256:7fc3add9e6003e026da5fc9e59b131b8f22b428b991ccd53e2af8071687b4fce", - "sha256:9e8f199bf7f97bd7ecebffcae45ebf9527603549b2b562df0fbc6d4d688f14ca", - "sha256:a109916aaac42bff84702fb5187f3edadbc7c97fc2c99c5ff81dd15dcce0d1e5", - "sha256:b848dbe1d57ed7c191dfc4ea64b8b004a3f9ece4bf4d0d80a367b76df20bf36e", - "sha256:f3ecdef226b9af856075f28227ff2c90ce3a594d092c39bee5513573f25e2714" + "sha256:018db9056b9d75eb93d12a9d35120f97a84d9a919bcab11ed56ad2d399d6e8dd", + "sha256:510ed78cd0980f6d3218099e874714cdf0d8a95582e7b059b06cabad855ed0a0", + "sha256:532627e8fdd825cf8767a2d2b94d77e874d5ddb0adefb04b237f7cc296748681", + "sha256:6206afcb2d90181ae8722798dcb56dc76675ab67458ac24c0dd7d75d632ac9bd", + "sha256:66c3edeedb774a3508ae70d87b3a19786445fe9a068dd3585e0cefa8a77b83d0", + "sha256:6d7cc9e60f976cf3e873acb9a40fed04afb5d224608ed5c1a105db4a3f09c5b6", + "sha256:853db610214e77ee817ecf0514e0d1d052dff7f63a0c157aa6eabae98db8a8de", + "sha256:d001a73c8bc2bf5b5c1360d59dd7573744e163b3607fa92788b7f3d5fefbd9a5", + "sha256:dde74af0fa774fa98892209992295adbfb91da3fa98c8f67a88afe8f5a349add", + "sha256:dde9fcaa24e7a9654f4baf2a55250b13a5ea701493d904c54069776b99a8216b", + "sha256:eef7a8a2f4318e2cb2dee8666d26e58eaf437c14788f3a2911d0c3da40405ae8" ], "markers": "python_version >= '3.8'", - "version": "==5.27.2" + "version": "==5.28.0" }, "py": { "hashes": [ @@ -1688,19 +1665,17 @@ }, "pyasn1": { "hashes": [ - "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c", - "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473" + "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034" ], "markers": "python_version >= '3.8'", - "version": "==0.6.0" + "version": "==0.6.1" }, "pyasn1-modules": { "hashes": [ - "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6", - "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b" + "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c" ], "markers": "python_version >= '3.8'", - "version": "==0.4.0" + "version": "==0.4.1" }, "pycodestyle": { "hashes": [ @@ -1728,11 +1703,11 @@ }, "pyparsing": { "hashes": [ - "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad", - "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742" + "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c", + "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032" ], "markers": "python_version >= '3.1'", - "version": "==3.1.2" + "version": "==3.1.4" }, "pytest": { "hashes": [ diff --git a/abr-testing/abr_testing/automation/google_sheets_tool.py b/abr-testing/abr_testing/automation/google_sheets_tool.py index b45684dcea0..c150eb93f5e 100644 --- a/abr-testing/abr_testing/automation/google_sheets_tool.py +++ b/abr-testing/abr_testing/automation/google_sheets_tool.py @@ -117,7 +117,7 @@ def batch_delete_rows(self, row_indices: List[int]) -> None: def batch_update_cells( self, data: List[List[Any]], - start_column: str, + start_column_index: Any, start_row: int, sheet_id: str, ) -> None: @@ -132,7 +132,8 @@ def column_letter_to_index(column_letter: str) -> int: requests = [] user_entered_value: Dict[str, Any] = {} - start_column_index = column_letter_to_index(start_column) - 1 + if type(start_column_index) == str: + start_column_index = column_letter_to_index(start_column_index) - 1 for col_offset, col_values in enumerate(data): column_index = start_column_index + col_offset @@ -223,9 +224,9 @@ def get_sheet_by_name(self, title: str) -> None: ) def token_check(self) -> None: - """Check if still credentials are still logged in.""" - if self.credentials.access_token_expired: - self.gc.login() + """Check if credentials are still valid and refresh if expired.""" + if self.credentials.expired: + self.credentials.refresh() # Refresh the credentials def get_row_index_with_value(self, some_string: str, col_num: int) -> Any: """Find row index of string by looking in specific column.""" diff --git a/abr-testing/abr_testing/automation/jira_tool.py b/abr-testing/abr_testing/automation/jira_tool.py index 6f81503ec42..d43db612561 100644 --- a/abr-testing/abr_testing/automation/jira_tool.py +++ b/abr-testing/abr_testing/automation/jira_tool.py @@ -51,8 +51,12 @@ def issues_on_board(self, project_key: str) -> List[List[Any]]: def match_issues(self, issue_ids: List[List[str]], ticket_summary: str) -> List: """Matches related ticket ID's.""" to_link = [] - error = ticket_summary.split("_")[3] - robot = ticket_summary.split("_")[0] + try: + error = ticket_summary.split("_")[3] + robot = ticket_summary.split("_")[0] + except IndexError: + error = "" + robot = "" # for every issue see if both match, if yes then grab issue ID and add it to a list for issue in issue_ids: summary = issue[1] diff --git a/abr-testing/abr_testing/data_collection/abr_google_drive.py b/abr-testing/abr_testing/data_collection/abr_google_drive.py index 3bd03cf3e3d..f1fe6ad0c8f 100644 --- a/abr-testing/abr_testing/data_collection/abr_google_drive.py +++ b/abr-testing/abr_testing/data_collection/abr_google_drive.py @@ -65,13 +65,7 @@ def create_data_dictionary( left_pipette = file_results.get("left", "") right_pipette = file_results.get("right", "") extension = file_results.get("extension", "") - ( - num_of_errors, - error_type, - error_code, - error_instrument, - error_level, - ) = read_robot_logs.get_error_info(file_results) + error_dict = read_robot_logs.get_error_info(file_results) all_modules = get_modules(file_results) @@ -99,7 +93,7 @@ def create_data_dictionary( pass # Handle datetime parsing errors if necessary if run_time_min > 0: - row = { + run_row = { "Robot": robot, "Run_ID": run_id, "Protocol_Name": protocol_name, @@ -108,15 +102,13 @@ def create_data_dictionary( "Start_Time": start_time_str, "End_Time": complete_time_str, "Run_Time (min)": run_time_min, - "Errors": num_of_errors, - "Error_Code": error_code, - "Error_Type": error_type, - "Error_Instrument": error_instrument, - "Error_Level": error_level, + } + instrument_row = { "Left Mount": left_pipette, "Right Mount": right_pipette, "Extension": extension, } + row = {**run_row, **error_dict, **instrument_row} tc_dict = read_robot_logs.thermocycler_commands(file_results) hs_dict = read_robot_logs.hs_commands(file_results) tm_dict = read_robot_logs.temperature_module_commands(file_results) @@ -128,7 +120,11 @@ def create_data_dictionary( "Average Temp (oC)": "", "Average RH(%)": "", } - row_for_lpc = {**row, **all_modules, **notes} + row_for_lpc = { + **row, + **all_modules, + **notes, + } row_2 = { **row, **all_modules, diff --git a/abr-testing/abr_testing/data_collection/abr_robot_error.py b/abr-testing/abr_testing/data_collection/abr_robot_error.py index 76a3f09ec18..9f87f7d4c46 100644 --- a/abr-testing/abr_testing/data_collection/abr_robot_error.py +++ b/abr-testing/abr_testing/data_collection/abr_robot_error.py @@ -62,7 +62,9 @@ def compare_current_trh_to_average( df_all_run_data["Start_Time"] = pd.to_datetime( df_all_run_data["Start_Time"], format="mixed", utc=True ).dt.tz_localize(None) - df_all_run_data["Errors"] = pd.to_numeric(df_all_run_data["Errors"]) + df_all_run_data["Run Ending Error"] = pd.to_numeric( + df_all_run_data["Run Ending Error"] + ) df_all_run_data["Average Temp (oC)"] = pd.to_numeric( df_all_run_data["Average Temp (oC)"] ) @@ -70,7 +72,7 @@ def compare_current_trh_to_average( (df_all_run_data["Robot"] == robot) & (df_all_run_data["Start_Time"] >= weeks_ago_3) & (df_all_run_data["Start_Time"] <= start_time) - & (df_all_run_data["Errors"] < 1) + & (df_all_run_data["Run Ending Error"] < 1) & (df_all_run_data["Average Temp (oC)"] > 1) ) @@ -122,7 +124,7 @@ def compare_lpc_to_historical_data( & (df_lpc_data["Robot"] == robot) & (df_lpc_data["Module"] == labware_dict["Module"]) & (df_lpc_data["Adapter"] == labware_dict["Adapter"]) - & (df_lpc_data["Errors"] < 1) + & (df_lpc_data["Run Ending Error"] < 1) ] # Converts coordinates to floats and finds averages. x_float = [float(value) for value in relevant_lpc["X"]] @@ -330,18 +332,17 @@ def get_run_error_info_from_robot( ip, results, storage_directory ) # Error Printout - ( - num_of_errors, - error_type, - error_code, - error_instrument, - error_level, - ) = read_robot_logs.get_error_info(results) + error_dict = read_robot_logs.get_error_info(results) + error_level = error_dict["Error_Level"] + error_type = error_dict["Error_Type"] + error_code = error_dict["Error_Code"] + error_instrument = error_dict["Error_Instrument"] # JIRA Ticket Fields + failure_level = "Level " + str(error_level) + " Failure" components = [failure_level, "Flex-RABR"] - components = match_error_to_component("RABR", error_type, components) + components = match_error_to_component("RABR", str(error_type), components) print(components) affects_version = results["API_Version"] parent = results.get("robot_name", "") diff --git a/abr-testing/abr_testing/data_collection/read_robot_logs.py b/abr-testing/abr_testing/data_collection/read_robot_logs.py index 740adbf0cb6..96182609c49 100644 --- a/abr-testing/abr_testing/data_collection/read_robot_logs.py +++ b/abr-testing/abr_testing/data_collection/read_robot_logs.py @@ -76,6 +76,36 @@ def command_time(command: Dict[str, str]) -> float: return start_to_complete +def count_command_in_run_data( + commands: List[Dict[str, Any]], command_of_interest: str, find_avg_time: bool +) -> Tuple[int, float]: + """Count number of times command occurs in a run.""" + total_command = 0 + total_time = 0.0 + for command in commands: + command_type = command["commandType"] + if command_type == command_of_interest: + total_command += 1 + if find_avg_time: + started_at = command.get("startedAt", "") + completed_at = command.get("completedAt", "") + + if started_at and completed_at: + try: + start_time = datetime.strptime( + started_at, "%Y-%m-%dT%H:%M:%S.%f%z" + ) + end_time = datetime.strptime( + completed_at, "%Y-%m-%dT%H:%M:%S.%f%z" + ) + total_time += (end_time - start_time).total_seconds() + except ValueError: + # Handle case where date parsing fails + pass + avg_time = total_time / total_command if total_command > 0 else 0.0 + return total_command, avg_time + + def instrument_commands(file_results: Dict[str, Any]) -> Dict[str, float]: """Count number of pipette and gripper commands per run.""" pipettes = file_results.get("pipettes", "") @@ -89,6 +119,7 @@ def instrument_commands(file_results: Dict[str, Any]) -> Dict[str, float]: right_pipette_id = "" left_pipette_id = "" gripper_pickups = 0.0 + avg_liquid_probe_time_sec = 0.0 # Match pipette mount to id for pipette in pipettes: if pipette["mount"] == "right": @@ -120,6 +151,9 @@ def instrument_commands(file_results: Dict[str, Any]) -> Dict[str, float]: and command["params"]["strategy"] == "usingGripper" ): gripper_pickups += 1 + liquid_probes, avg_liquid_probe_time_sec = count_command_in_run_data( + commandData, "liquidProbe", True + ) pipette_dict = { "Left Pipette Total Tip Pick Up(s)": left_tip_pick_up, "Left Pipette Total Aspirates": left_aspirate, @@ -128,6 +162,8 @@ def instrument_commands(file_results: Dict[str, Any]) -> Dict[str, float]: "Right Pipette Total Aspirates": right_aspirate, "Right Pipette Total Dispenses": right_dispense, "Gripper Pick Ups": gripper_pickups, + "Total Liquid Probes": liquid_probes, + "Average Liquid Probe Time (sec)": avg_liquid_probe_time_sec, } return pipette_dict @@ -362,50 +398,58 @@ def create_abr_data_sheet( return sheet_location -def get_error_info(file_results: Dict[str, Any]) -> Tuple[int, str, str, str, str]: +def get_error_info(file_results: Dict[str, Any]) -> Dict[str, Any]: """Determines if errors exist in run log and documents them.""" - error_levels = [] - error_level = "" # Read error levels file with open(ERROR_LEVELS_PATH, "r") as error_file: - error_levels = list(csv.reader(error_file)) - num_of_errors = len(file_results["errors"]) - if num_of_errors == 0: - error_type = "" - error_code = "" - error_instrument = "" - error_level = "" - return 0, error_type, error_code, error_instrument, error_level + error_levels = {row[1]: row[4] for row in csv.reader(error_file)} + # Initialize Variables + recoverable_errors: Dict[str, int] = dict() + total_recoverable_errors = 0 + end_run_errors = len(file_results["errors"]) commands_of_run: List[Dict[str, Any]] = file_results.get("commands", []) + error_recovery = file_results.get("hasEverEnteredErrorRecovery", False) + # Count recoverable errors + if error_recovery: + for command in commands_of_run: + error_info = command.get("error", {}) + if error_info.get("isDefined"): + total_recoverable_errors += 1 + error_type = error_info.get("errorType", "") + recoverable_errors[error_type] = ( + recoverable_errors.get(error_type, 0) + 1 + ) + # Get run-ending error info try: - run_command_error: Dict[str, Any] = commands_of_run[-1] - error_str: int = len(run_command_error.get("error", "")) - except IndexError: - error_str = 0 - if error_str > 1: - error_type = run_command_error["error"].get("errorType", "") + run_command_error = commands_of_run[-1]["error"] + error_type = run_command_error.get("errorType", "") if error_type == "PythonException": - # Reassign error_type to be more descriptive - error_type = run_command_error.get("detail", "").split(":")[0] - error_code = run_command_error["error"].get("errorCode", "") + error_type = commands_of_run[-1].get("detail", "").split(":")[0] + error_code = run_command_error.get("errorCode", "") + error_instrument = run_command_error.get("errorInfo", {}).get( + "node", run_command_error.get("errorInfo", {}).get("port", "") + ) + except (IndexError, KeyError): try: - # Instrument Error - error_instrument = run_command_error["error"]["errorInfo"]["node"] - except KeyError: - # Module - error_instrument = run_command_error["error"]["errorInfo"].get("port", "") - else: - error_type = file_results["errors"][0]["errorType"] - error_code = file_results["errors"][0]["errorCode"] - error_instrument = file_results["errors"][0]["detail"] - for error in error_levels: - code_error = error[1] - if code_error == error_code: - error_level = error[4] - if len(error_level) < 1: - error_level = str(4) - - return num_of_errors, error_type, error_code, error_instrument, error_level + error_details = file_results.get("errors", [{}])[0] + except IndexError: + error_details = {} + error_type = error_details.get("errorType", "") + error_code = error_details.get("errorCode", "") + error_instrument = error_details.get("detail", "") + # Determine error level + error_level = error_levels.get(error_code, "4") + # Create dictionary with all error descriptions + error_dict = { + "Total Recoverable Error(s)": total_recoverable_errors, + "Recoverable Error(s) Description": recoverable_errors, + "Run Ending Error": end_run_errors, + "Error_Code": error_code, + "Error_Type": error_type, + "Error_Instrument": error_instrument, + "Error_Level": error_level, + } + return error_dict def write_to_local_and_google_sheet( @@ -570,10 +614,10 @@ def get_calibration_offsets( def get_logs(storage_directory: str, ip: str) -> List[str]: """Get Robot logs.""" log_types: List[Dict[str, Any]] = [ - {"log type": "api.log", "records": 1000}, + {"log type": "api.log", "records": 10000}, {"log type": "server.log", "records": 10000}, {"log type": "serial.log", "records": 10000}, - {"log type": "touchscreen.log", "records": 1000}, + {"log type": "touchscreen.log", "records": 10000}, ] all_paths = [] for log_type in log_types: diff --git a/abr-testing/abr_testing/tools/sync_abr_sheet.py b/abr-testing/abr_testing/tools/sync_abr_sheet.py index 1bae79cd159..aca116292a8 100644 --- a/abr-testing/abr_testing/tools/sync_abr_sheet.py +++ b/abr-testing/abr_testing/tools/sync_abr_sheet.py @@ -15,6 +15,7 @@ def determine_lifetime(abr_google_sheet: Any) -> None: """Record lifetime % of robot, pipettes, and gripper per run.""" # Get all data headers = abr_google_sheet.get_row(1) + lifetime_index = headers.index("Robot Lifetime (%)") all_google_data = abr_google_sheet.get_all_data(expected_headers=headers) # Convert dictionary to pandas dataframe df_sheet_data = pd.DataFrame.from_dict(all_google_data) @@ -92,7 +93,9 @@ def determine_lifetime(abr_google_sheet: Any) -> None: [right_pipette_percent_lifetime], [gripper_percent_lifetime], ] - abr_google_sheet.batch_update_cells(update_list, "AV", row_num, "0") + abr_google_sheet.batch_update_cells( + update_list, lifetime_index, row_num, "0" + ) print(f"Updated row {row_num} for run: {run_id}") @@ -101,6 +104,10 @@ def compare_run_to_temp_data( ) -> None: """Read ABR Data and compare robot and timestamp columns to temp data.""" row_update = 0 + # Get column number for average temp and rh + headers = google_sheet.get_row(1) + temp_index = headers.index("Average Temp (oC)") + 1 + rh_index = headers.index("Average RH(%)") + 1 for run in abr_data: run_id = run["Run_ID"] try: @@ -129,9 +136,9 @@ def compare_run_to_temp_data( avg_humidity = mean(rel_hums) row_num = google_sheet.get_row_index_with_value(run_id, 2) # Write average temperature - google_sheet.update_cell("Sheet1", row_num, 46, avg_temps) + google_sheet.update_cell("Sheet1", row_num, temp_index, avg_temps) # Write average humidity - google_sheet.update_cell("Sheet1", row_num, 47, avg_humidity) + google_sheet.update_cell("Sheet1", row_num, rh_index, avg_humidity) row_update += 1 # TODO: Write averages to google sheet print(f"Updated row {row_num}.") From b0b8e7f584db8ba9be370bd9135cb53b6c5cfe1a Mon Sep 17 00:00:00 2001 From: koji Date: Wed, 18 Sep 2024 09:14:56 -0400 Subject: [PATCH 19/24] fix(protocol-designer): fix Add custom labware button has no hover state (#16274) * fix(protocol-designer): fix Add custom labware button has no hover state --- .../localization/en/starting_deck_state.json | 6 +-- .../pages/Designer/DeckSetup/LabwareTools.tsx | 53 +++++++++++-------- .../pages/Designer/DeckSetup/ModuleLabel.tsx | 18 +++---- .../DeckSetup/__tests__/LabwareTools.test.tsx | 2 +- 4 files changed, 43 insertions(+), 36 deletions(-) diff --git a/protocol-designer/src/assets/localization/en/starting_deck_state.json b/protocol-designer/src/assets/localization/en/starting_deck_state.json index 322629fc02c..6886098ddbf 100644 --- a/protocol-designer/src/assets/localization/en/starting_deck_state.json +++ b/protocol-designer/src/assets/localization/en/starting_deck_state.json @@ -8,10 +8,9 @@ "add_module": "Add a module", "add_rest": "Add labware and liquids to complete deck setup", "aluminumBlock": "Aluminum block", + "clear_labware": "Clear labware", "clear_slot": "Clear slot", "clear": "Clear", - "clear_labware": "Clear labware", - "custom_labware": "Add custom labware", "custom": "Custom labware definitions", "customize_slot": "Customize slot", "deck_hardware": "Deck hardware", @@ -28,11 +27,11 @@ "heater_shaker_trash": "The heater-shaker cannot be next to the trash bin", "labware": "Labware", "liquids": "Liquids", + "no_offdeck_labware": "No off-deck labware added", "off_deck_labware": "Off-deck Labware", "off_deck_title": "Off deck", "offDeck": "Off-deck", "onDeck": "On deck", - "no_offdeck_labware": "No off-deck labware added", "one_item": "No more than 1 {{hardware}} allowed on the deck at one time", "only_display_rec": "Only display recommended labware", "protocol_starting_deck": "Protocol starting deck", @@ -45,6 +44,7 @@ "trash_required": "A trash bin or waste chute is required", "tubeRack": "Tube rack", "untitled_protocol": "Untitled protocol", + "upload_custom_labware": "Upload custom labware", "we_added_hardware": "We've added your deck hardware!", "wellPlate": "Well plate" } diff --git a/protocol-designer/src/pages/Designer/DeckSetup/LabwareTools.tsx b/protocol-designer/src/pages/Designer/DeckSetup/LabwareTools.tsx index c1dc9c2e33f..9da8dfab291 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/LabwareTools.tsx +++ b/protocol-designer/src/pages/Designer/DeckSetup/LabwareTools.tsx @@ -5,12 +5,12 @@ import styled from 'styled-components' import { useDispatch, useSelector } from 'react-redux' import { ALIGN_CENTER, - COLORS, CheckboxField, DIRECTION_COLUMN, DISPLAY_INLINE_BLOCK, Flex, InputField, + JUSTIFY_CENTER, ListButton, ListButtonAccordion, ListButtonAccordionContainer, @@ -31,6 +31,7 @@ import { getModuleType, } from '@opentrons/shared-data' +import { BUTTON_LINK_STYLE } from '../../../atoms' import { selectors as stepFormSelectors } from '../../../step-forms' import { getOnlyLatestDefs } from '../../../labware-defs' import { @@ -207,12 +208,8 @@ export function LabwareTools(props: LabwareToolsProps): JSX.Element { } return ( - - + + {t('add_labware')} @@ -250,7 +247,11 @@ export function LabwareTools(props: LabwareToolsProps): JSX.Element { ) : null} - + {customLabwareURIs.length === 0 ? null : ( - - - {t('custom_labware')} - - { - setSelectedCategory(CUSTOM_CATEGORY) - dispatch(createCustomLabwareDef(e)) - }} - /> - + + + + {t('upload_custom_labware')} + + { + setSelectedCategory(CUSTOM_CATEGORY) + dispatch(createCustomLabwareDef(e)) + }} + /> + + ) } const StyledLabel = styled.label` text-decoration: ${TYPOGRAPHY.textDecorationUnderline}; - text-align: ${TYPOGRAPHY.textAlignCenter}}; - display: ${DISPLAY_INLINE_BLOCK} + text-align: ${TYPOGRAPHY.textAlignCenter}; + display: ${DISPLAY_INLINE_BLOCK}; cursor: pointer; input[type='file'] { display: none; diff --git a/protocol-designer/src/pages/Designer/DeckSetup/ModuleLabel.tsx b/protocol-designer/src/pages/Designer/DeckSetup/ModuleLabel.tsx index 91fa875d1dc..e527a001d2f 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/ModuleLabel.tsx +++ b/protocol-designer/src/pages/Designer/DeckSetup/ModuleLabel.tsx @@ -37,7 +37,7 @@ export const ModuleLabel = (props: ModuleLabelProps): JSX.Element => { const def = getModuleDef2(moduleModel) const overhang = - def != null && def?.dimensions.labwareInterfaceXDimension != null + def?.dimensions.labwareInterfaceXDimension != null ? def.dimensions.xDimension - def?.dimensions.labwareInterfaceXDimension : 0 // TODO(ja 9/6/24): definitely need to refine these overhang values @@ -55,21 +55,21 @@ export const ModuleLabel = (props: ModuleLabelProps): JSX.Element => { ref={labelContainerRef} deckLabels={[ { - text: def.displayName, - isSelected: isSelected, - isLast: isLast, - moduleModel: def.model, + text: def?.displayName, + isSelected, + isLast, + moduleModel: def?.model, }, ...labwareInfos, ]} x={ (orientation === 'right' ? position[0] - overhang - : position[0] - leftOverhang) - def.cornerOffsetFromSlot.x + : position[0] - leftOverhang) - def?.cornerOffsetFromSlot.x } - y={position[1] + def.cornerOffsetFromSlot.y - labelContainerHeight} - width={def.dimensions.xDimension + 2} - height={def.dimensions.yDimension + 2} + y={position[1] + def?.cornerOffsetFromSlot.y - labelContainerHeight} + width={def?.dimensions.xDimension + 2} + height={def?.dimensions.yDimension + 2} /> ) } diff --git a/protocol-designer/src/pages/Designer/DeckSetup/__tests__/LabwareTools.test.tsx b/protocol-designer/src/pages/Designer/DeckSetup/__tests__/LabwareTools.test.tsx index 603b9dcfe5d..a1f1127e5e6 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/__tests__/LabwareTools.test.tsx +++ b/protocol-designer/src/pages/Designer/DeckSetup/__tests__/LabwareTools.test.tsx @@ -123,7 +123,7 @@ describe('LabwareTools', () => { it('renders the custom labware flow', () => { render(props) - screen.getByText('Add custom labware') + screen.getByText('Upload custom labware') fireEvent.change(screen.getByTestId('customLabwareInput')) expect(vi.mocked(createCustomLabwareDef)).toHaveBeenCalled() }) From b17b17cd380ab7c99eebe13585c3f5720daa8feb Mon Sep 17 00:00:00 2001 From: Max Marrone Date: Wed, 18 Sep 2024 09:43:57 -0400 Subject: [PATCH 20/24] refactor(api): Port `touchTip`, `liquidProbe`, and `tryLiquidProbe` location updates to `StateUpdate` (#16261) ## Overview More incremental work towards EXEC-652. ## Changelog This continues the pattern started in #16160. The following commands now use the new `StateUpdate` mechanism to update the pipette's logical state for the purposes of path planning: * `touchTip` * `liquidProbe` * `tryLiquidProbe` --- .../commands/command_unions.py | 3 +- .../protocol_engine/commands/liquid_probe.py | 175 ++++++++++-------- .../commands/pipetting_common.py | 9 - .../protocol_engine/commands/touch_tip.py | 19 +- .../protocol_engine/state/pipettes.py | 29 --- .../commands/test_liquid_probe.py | 91 ++------- .../commands/test_touch_tip.py | 11 +- .../state/test_pipette_store.py | 130 ------------- .../tests/runs/test_error_recovery_mapping.py | 9 +- 9 files changed, 146 insertions(+), 330 deletions(-) diff --git a/api/src/opentrons/protocol_engine/commands/command_unions.py b/api/src/opentrons/protocol_engine/commands/command_unions.py index 27e97976ad0..ca39553e743 100644 --- a/api/src/opentrons/protocol_engine/commands/command_unions.py +++ b/api/src/opentrons/protocol_engine/commands/command_unions.py @@ -11,7 +11,6 @@ from .pipetting_common import ( OverpressureError, LiquidNotFoundError, - LiquidNotFoundErrorInternalData, ) from . import absorbance_reader @@ -706,7 +705,7 @@ CommandDefinedErrorData = Union[ DefinedErrorData[TipPhysicallyMissingError, None], DefinedErrorData[OverpressureError, None], - DefinedErrorData[LiquidNotFoundError, LiquidNotFoundErrorInternalData], + DefinedErrorData[LiquidNotFoundError, None], ] diff --git a/api/src/opentrons/protocol_engine/commands/liquid_probe.py b/api/src/opentrons/protocol_engine/commands/liquid_probe.py index ecf932a3470..222cf6ed608 100644 --- a/api/src/opentrons/protocol_engine/commands/liquid_probe.py +++ b/api/src/opentrons/protocol_engine/commands/liquid_probe.py @@ -1,20 +1,21 @@ """The liquidProbe and tryLiquidProbe commands.""" from __future__ import annotations -from typing import TYPE_CHECKING, Optional, Type, Union +from typing import TYPE_CHECKING, NamedTuple, Optional, Type, Union +from typing_extensions import Literal + +from pydantic import Field + from opentrons.protocol_engine.errors.exceptions import MustHomeError, TipNotEmptyError +from opentrons.protocol_engine.state import update_types from opentrons.types import MountType from opentrons_shared_data.errors.exceptions import ( PipetteLiquidNotFoundError, ) -from typing_extensions import Literal - -from pydantic import Field from ..types import DeckPoint from .pipetting_common import ( LiquidNotFoundError, - LiquidNotFoundErrorInternalData, PipetteIdMixin, WellLocationMixin, DestinationPositionResult, @@ -80,11 +81,76 @@ class TryLiquidProbeResult(DestinationPositionResult): _LiquidProbeExecuteReturn = Union[ SuccessData[LiquidProbeResult, None], - DefinedErrorData[LiquidNotFoundError, LiquidNotFoundErrorInternalData], + DefinedErrorData[LiquidNotFoundError, None], ] _TryLiquidProbeExecuteReturn = SuccessData[TryLiquidProbeResult, None] +class _ExecuteCommonResult(NamedTuple): + # If the probe succeeded, the z_pos that it returned. + # Or, if the probe found no liquid, the error representing that, + # so calling code can propagate those details up. + z_pos_or_error: float | PipetteLiquidNotFoundError + + state_update: update_types.StateUpdate + deck_point: DeckPoint + + +async def _execute_common( + movement: MovementHandler, pipetting: PipettingHandler, params: _CommonParams +) -> _ExecuteCommonResult: + pipette_id = params.pipetteId + labware_id = params.labwareId + well_name = params.wellName + + state_update = update_types.StateUpdate() + + # _validate_tip_attached in pipetting.py is a private method so we're using + # get_is_ready_to_aspirate as an indirect way to throw a TipNotAttachedError if appropriate + pipetting.get_is_ready_to_aspirate(pipette_id=pipette_id) + + if pipetting.get_is_empty(pipette_id=pipette_id) is False: + raise TipNotEmptyError( + message="This operation requires a tip with no liquid in it." + ) + + if await movement.check_for_valid_position(mount=MountType.LEFT) is False: + raise MustHomeError( + message="Current position of pipette is invalid. Please home." + ) + + # liquid_probe process start position + position = await movement.move_to_well( + pipette_id=pipette_id, + labware_id=labware_id, + well_name=well_name, + well_location=params.wellLocation, + ) + deck_point = DeckPoint.construct(x=position.x, y=position.y, z=position.z) + state_update.set_pipette_location( + pipette_id=pipette_id, + new_labware_id=labware_id, + new_well_name=well_name, + new_deck_point=deck_point, + ) + + try: + z_pos = await pipetting.liquid_probe_in_place( + pipette_id=pipette_id, + labware_id=labware_id, + well_name=well_name, + well_location=params.wellLocation, + ) + except PipetteLiquidNotFoundError as exception: + return _ExecuteCommonResult( + z_pos_or_error=exception, state_update=state_update, deck_point=deck_point + ) + else: + return _ExecuteCommonResult( + z_pos_or_error=z_pos, state_update=state_update, deck_point=deck_point + ) + + class LiquidProbeImplementation( AbstractCommandImpl[LiquidProbeParams, _LiquidProbeExecuteReturn] ): @@ -115,40 +181,10 @@ async def execute(self, params: _CommonParams) -> _LiquidProbeExecuteReturn: MustHomeError: as an undefined error, if the plunger is not in a valid position. """ - pipette_id = params.pipetteId - labware_id = params.labwareId - well_name = params.wellName - - # _validate_tip_attached in pipetting.py is a private method so we're using - # get_is_ready_to_aspirate as an indirect way to throw a TipNotAttachedError if appropriate - self._pipetting.get_is_ready_to_aspirate(pipette_id=pipette_id) - - if self._pipetting.get_is_empty(pipette_id=pipette_id) is False: - raise TipNotEmptyError( - message="This operation requires a tip with no liquid in it." - ) - - if await self._movement.check_for_valid_position(mount=MountType.LEFT) is False: - raise MustHomeError( - message="Current position of pipette is invalid. Please home." - ) - - # liquid_probe process start position - position = await self._movement.move_to_well( - pipette_id=pipette_id, - labware_id=labware_id, - well_name=well_name, - well_location=params.wellLocation, + z_pos_or_error, state_update, deck_point = await _execute_common( + self._movement, self._pipetting, params ) - - try: - z_pos = await self._pipetting.liquid_probe_in_place( - pipette_id=pipette_id, - labware_id=labware_id, - well_name=well_name, - well_location=params.wellLocation, - ) - except PipetteLiquidNotFoundError as e: + if isinstance(z_pos_or_error, PipetteLiquidNotFoundError): return DefinedErrorData( public=LiquidNotFoundError( id=self._model_utils.generate_id(), @@ -157,21 +193,20 @@ async def execute(self, params: _CommonParams) -> _LiquidProbeExecuteReturn: ErrorOccurrence.from_failed( id=self._model_utils.generate_id(), createdAt=self._model_utils.get_timestamp(), - error=e, + error=z_pos_or_error, ) ], ), - private=LiquidNotFoundErrorInternalData( - position=DeckPoint(x=position.x, y=position.y, z=position.z) - ), + private=None, + state_update=state_update, ) else: return SuccessData( public=LiquidProbeResult( - z_position=z_pos, - position=DeckPoint(x=position.x, y=position.y, z=position.z), + z_position=z_pos_or_error, position=deck_point ), private=None, + state_update=state_update, ) @@ -184,12 +219,10 @@ def __init__( self, movement: MovementHandler, pipetting: PipettingHandler, - model_utils: ModelUtils, **kwargs: object, ) -> None: self._movement = movement self._pipetting = pipetting - self._model_utils = model_utils async def execute(self, params: _CommonParams) -> _TryLiquidProbeExecuteReturn: """Execute a `tryLiquidProbe` command. @@ -198,39 +231,23 @@ async def execute(self, params: _CommonParams) -> _TryLiquidProbeExecuteReturn: found, `tryLiquidProbe` returns a success result with `z_position=null` instead of a defined error. """ - # We defer to the `liquidProbe` implementation. If it returns a defined - # `liquidNotFound` error, we remap that to a success result. - # Otherwise, we return the result or propagate the exception unchanged. - - original_impl = LiquidProbeImplementation( - movement=self._movement, - pipetting=self._pipetting, - model_utils=self._model_utils, + z_pos_or_error, state_update, deck_point = await _execute_common( + self._movement, self._pipetting, params + ) + + z_pos = ( + None + if isinstance(z_pos_or_error, PipetteLiquidNotFoundError) + else z_pos_or_error + ) + return SuccessData( + public=TryLiquidProbeResult( + z_position=z_pos, + position=deck_point, + ), + private=None, + state_update=state_update, ) - original_result = await original_impl.execute(params) - - match original_result: - case DefinedErrorData( - public=LiquidNotFoundError(), - private=LiquidNotFoundErrorInternalData() as original_private, - ): - return SuccessData( - public=TryLiquidProbeResult( - z_position=None, - position=original_private.position, - ), - private=None, - ) - case SuccessData( - public=LiquidProbeResult() as original_public, private=None - ): - return SuccessData( - public=TryLiquidProbeResult( - position=original_public.position, - z_position=original_public.z_position, - ), - private=None, - ) class LiquidProbe( diff --git a/api/src/opentrons/protocol_engine/commands/pipetting_common.py b/api/src/opentrons/protocol_engine/commands/pipetting_common.py index 0c6f463ed5d..29aabcb78df 100644 --- a/api/src/opentrons/protocol_engine/commands/pipetting_common.py +++ b/api/src/opentrons/protocol_engine/commands/pipetting_common.py @@ -1,5 +1,4 @@ """Common pipetting command base models.""" -from dataclasses import dataclass from opentrons_shared_data.errors import ErrorCodes from pydantic import BaseModel, Field from typing import Literal, Optional, Tuple, TypedDict @@ -169,11 +168,3 @@ class LiquidNotFoundError(ErrorOccurrence): errorCode: str = ErrorCodes.PIPETTE_LIQUID_NOT_FOUND.value.code detail: str = ErrorCodes.PIPETTE_LIQUID_NOT_FOUND.value.detail - - -@dataclass(frozen=True) -class LiquidNotFoundErrorInternalData: - """Internal-to-ProtocolEngine data about a LiquidNotFoundError.""" - - position: DeckPoint - """Same meaning as DestinationPositionResult.position.""" diff --git a/api/src/opentrons/protocol_engine/commands/touch_tip.py b/api/src/opentrons/protocol_engine/commands/touch_tip.py index aa64d37559a..744b1c14107 100644 --- a/api/src/opentrons/protocol_engine/commands/touch_tip.py +++ b/api/src/opentrons/protocol_engine/commands/touch_tip.py @@ -4,6 +4,8 @@ from typing import TYPE_CHECKING, Optional, Type from typing_extensions import Literal +from opentrons.protocol_engine.state import update_types + from ..errors import TouchTipDisabledError, LabwareIsTipRackError from ..types import DeckPoint from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData @@ -71,6 +73,8 @@ async def execute( labware_id = params.labwareId well_name = params.wellName + state_update = update_types.StateUpdate() + if self._state_view.labware.get_has_quirk(labware_id, "touchTipDisabled"): raise TouchTipDisabledError( f"Touch tip not allowed on labware {labware_id}" @@ -98,14 +102,25 @@ async def execute( center_point=center_point, ) - x, y, z = await self._gantry_mover.move_to( + final_point = await self._gantry_mover.move_to( pipette_id=pipette_id, waypoints=touch_waypoints, speed=touch_speed, ) + final_deck_point = DeckPoint.construct( + x=final_point.x, y=final_point.y, z=final_point.z + ) + state_update.set_pipette_location( + pipette_id=pipette_id, + new_labware_id=labware_id, + new_well_name=well_name, + new_deck_point=final_deck_point, + ) return SuccessData( - public=TouchTipResult(position=DeckPoint(x=x, y=y, z=z)), private=None + public=TouchTipResult(position=final_deck_point), + private=None, + state_update=state_update, ) diff --git a/api/src/opentrons/protocol_engine/state/pipettes.py b/api/src/opentrons/protocol_engine/state/pipettes.py index 21d2dd6e114..0ff0bf847ba 100644 --- a/api/src/opentrons/protocol_engine/state/pipettes.py +++ b/api/src/opentrons/protocol_engine/state/pipettes.py @@ -22,7 +22,6 @@ ) from opentrons.protocol_engine.actions.actions import FailCommandAction from opentrons.protocol_engine.commands.command import DefinedErrorData -from opentrons.protocol_engine.commands.pipetting_common import LiquidNotFoundError from opentrons.types import MountType, Mount as HwMount, Point from . import update_types @@ -320,33 +319,6 @@ def _update_current_location( # noqa: C901 # These commands leave the pipette in a new location. # Update current_location to reflect that. if isinstance(action, SucceedCommandAction) and isinstance( - action.command.result, - ( - commands.TouchTipResult, - commands.LiquidProbeResult, - commands.TryLiquidProbeResult, - ), - ): - self._state.current_location = CurrentWell( - pipette_id=action.command.params.pipetteId, - labware_id=action.command.params.labwareId, - well_name=action.command.params.wellName, - ) - elif isinstance(action, FailCommandAction) and ( - isinstance(action.error, DefinedErrorData) - and ( - ( - isinstance(action.running_command, commands.LiquidProbe) - and isinstance(action.error.public, LiquidNotFoundError) - ) - ) - ): - self._state.current_location = CurrentWell( - pipette_id=action.running_command.params.pipetteId, - labware_id=action.running_command.params.labwareId, - well_name=action.running_command.params.wellName, - ) - elif isinstance(action, SucceedCommandAction) and isinstance( action.command.result, ( commands.MoveToAddressableAreaResult, @@ -440,7 +412,6 @@ def _update_deck_point( # noqa: C901 commands.MoveRelativeResult, commands.MoveToAddressableAreaResult, commands.MoveToAddressableAreaForDropTipResult, - commands.TouchTipResult, ), ): pipette_id = action.command.params.pipetteId diff --git a/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py b/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py index f3fb6c7f18c..0bbb68cd52e 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py +++ b/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py @@ -13,10 +13,8 @@ from decoy import matchers, Decoy import pytest -from opentrons.protocol_engine.commands.pipetting_common import ( - LiquidNotFoundError, - LiquidNotFoundErrorInternalData, -) +from opentrons.protocol_engine.commands.pipetting_common import LiquidNotFoundError +from opentrons.protocol_engine.state import update_types from opentrons.types import MountType, Point from opentrons.protocol_engine import WellLocation, WellOrigin, WellOffset, DeckPoint @@ -30,14 +28,12 @@ ) from opentrons.protocol_engine.commands.command import DefinedErrorData, SuccessData -from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import ( MovementHandler, PipettingHandler, ) from opentrons.protocol_engine.resources.model_utils import ModelUtils -from opentrons.protocol_engine.types import LoadedPipette EitherImplementationType = Union[ @@ -96,7 +92,7 @@ def subject( ) -async def test_liquid_probe_implementation_no_prep( +async def test_liquid_probe_implementation( decoy: Decoy, movement: MovementHandler, pipetting: PipettingHandler, @@ -104,7 +100,7 @@ async def test_liquid_probe_implementation_no_prep( params_type: EitherParamsType, result_type: EitherResultType, ) -> None: - """A Liquid Probe should have an execution implementation without preparing to aspirate.""" + """It should move to the destination and do a liquid probe there.""" location = WellLocation(origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1)) data = params_type( @@ -140,66 +136,12 @@ async def test_liquid_probe_implementation_no_prep( assert result == SuccessData( public=result_type(z_position=15.0, position=DeckPoint(x=1, y=2, z=3)), private=None, - ) - - -async def test_liquid_probe_implementation_with_prep( - decoy: Decoy, - state_view: StateView, - movement: MovementHandler, - pipetting: PipettingHandler, - subject: EitherImplementation, - params_type: EitherParamsType, - result_type: EitherResultType, -) -> None: - """A Liquid Probe should have an execution implementation with preparing to aspirate.""" - location = WellLocation(origin=WellOrigin.TOP, offset=WellOffset(x=0, y=0, z=2)) - - data = params_type( - pipetteId="abc", - labwareId="123", - wellName="A3", - wellLocation=location, - ) - - decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id="abc")).then_return(False) - - decoy.when(state_view.pipettes.get(pipette_id="abc")).then_return( - LoadedPipette.construct( # type:ignore[call-arg] - mount=MountType.LEFT - ) - ) - decoy.when( - await movement.move_to_well( - pipette_id="abc", labware_id="123", well_name="A3", well_location=location - ), - ).then_return(Point(x=1, y=2, z=3)) - - decoy.when( - await pipetting.liquid_probe_in_place( - pipette_id="abc", - labware_id="123", - well_name="A3", - well_location=location, - ), - ).then_return(15.0) - - result = await subject.execute(data) - - assert type(result.public) is result_type # Pydantic v1 only compares the fields. - assert result == SuccessData( - public=result_type(z_position=15.0, position=DeckPoint(x=1, y=2, z=3)), - private=None, - ) - - decoy.verify( - await movement.move_to_well( - pipette_id="abc", - labware_id="123", - well_name="A3", - well_location=WellLocation( - origin=WellOrigin.TOP, offset=WellOffset(x=0, y=0, z=2) - ), + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="abc", + new_location=update_types.Well(labware_id="123", well_name="A3"), + new_deck_point=DeckPoint(x=1, y=2, z=3), + ) ), ) @@ -259,6 +201,13 @@ async def test_liquid_not_found_error( result = await subject.execute(data) + expected_state_update = update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id=pipette_id, + new_location=update_types.Well(labware_id=labware_id, well_name=well_name), + new_deck_point=DeckPoint(x=position.x, y=position.y, z=position.z), + ) + ) if isinstance(subject, LiquidProbeImplementation): assert result == DefinedErrorData( public=LiquidNotFoundError.construct( @@ -266,9 +215,8 @@ async def test_liquid_not_found_error( createdAt=error_timestamp, wrappedErrors=[matchers.Anything()], ), - private=LiquidNotFoundErrorInternalData( - position=DeckPoint(x=position.x, y=position.y, z=position.z) - ), + private=None, + state_update=expected_state_update, ) else: assert result == SuccessData( @@ -277,6 +225,7 @@ async def test_liquid_not_found_error( position=DeckPoint(x=position.x, y=position.y, z=position.z), ), private=None, + state_update=expected_state_update, ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_touch_tip.py b/api/tests/opentrons/protocol_engine/commands/test_touch_tip.py index a1f9b11e8d5..f18e79e0a55 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_touch_tip.py +++ b/api/tests/opentrons/protocol_engine/commands/test_touch_tip.py @@ -6,6 +6,7 @@ from opentrons.motion_planning import Waypoint from opentrons.protocol_engine import WellLocation, WellOffset, DeckPoint, errors from opentrons.protocol_engine.execution import MovementHandler, GantryMover +from opentrons.protocol_engine.state import update_types from opentrons.protocol_engine.state.state import StateView from opentrons.types import Point @@ -122,7 +123,15 @@ async def test_touch_tip_implementation( result = await subject.execute(params) assert result == SuccessData( - public=TouchTipResult(position=DeckPoint(x=4, y=5, z=6)), private=None + public=TouchTipResult(position=DeckPoint(x=4, y=5, z=6)), + private=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="abc", + new_location=update_types.Well(labware_id="123", well_name="A3"), + new_deck_point=DeckPoint(x=4, y=5, z=6), + ) + ), ) diff --git a/api/tests/opentrons/protocol_engine/state/test_pipette_store.py b/api/tests/opentrons/protocol_engine/state/test_pipette_store.py index ed9e9ad2576..22c58b77e5b 100644 --- a/api/tests/opentrons/protocol_engine/state/test_pipette_store.py +++ b/api/tests/opentrons/protocol_engine/state/test_pipette_store.py @@ -9,12 +9,6 @@ from opentrons.protocol_engine.state import update_types from opentrons.types import DeckSlotName, MountType, Point from opentrons.protocol_engine import commands as cmd -from opentrons.protocol_engine.commands.command import DefinedErrorData -from opentrons.protocol_engine.commands.pipetting_common import ( - LiquidNotFoundError, - LiquidNotFoundErrorInternalData, -) -from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryType from opentrons.protocol_engine.types import ( CurrentAddressableArea, DeckPoint, @@ -54,7 +48,6 @@ create_drop_tip_in_place_command, create_succeeded_command, create_unsafe_drop_tip_in_place_command, - create_touch_tip_command, create_move_to_well_command, create_blow_out_command, create_blow_out_in_place_command, @@ -441,120 +434,6 @@ def test_blow_out_clears_volume( assert subject.state.aspirated_volume_by_id["pipette-id"] is None -@pytest.mark.parametrize( - ("action", "expected_location"), - ( - # liquidProbe and tryLiquidProbe succeeding and with overpressure error - ( - SucceedCommandAction( - command=cmd.LiquidProbe( - id="command-id", - createdAt=datetime.now(), - startedAt=datetime.now(), - completedAt=datetime.now(), - key="command-key", - status=cmd.CommandStatus.SUCCEEDED, - params=cmd.LiquidProbeParams( - labwareId="liquid-probe-labware-id", - wellName="liquid-probe-well-name", - pipetteId="pipette-id", - ), - result=cmd.LiquidProbeResult( - position=DeckPoint(x=0, y=0, z=0), z_position=0 - ), - ), - private_result=None, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="liquid-probe-labware-id", - well_name="liquid-probe-well-name", - ), - ), - ( - FailCommandAction( - running_command=cmd.LiquidProbe( - id="command-id", - createdAt=datetime.now(), - startedAt=datetime.now(), - key="command-key", - status=cmd.CommandStatus.RUNNING, - params=cmd.LiquidProbeParams( - labwareId="liquid-probe-labware-id", - wellName="liquid-probe-well-name", - pipetteId="pipette-id", - ), - ), - error=DefinedErrorData( - public=LiquidNotFoundError( - id="error-id", - createdAt=datetime.now(), - ), - private=LiquidNotFoundErrorInternalData( - position=DeckPoint(x=0, y=0, z=0) - ), - ), - command_id="command-id", - error_id="error-id", - failed_at=datetime.now(), - notes=[], - type=ErrorRecoveryType.WAIT_FOR_RECOVERY, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="liquid-probe-labware-id", - well_name="liquid-probe-well-name", - ), - ), - ( - SucceedCommandAction( - command=cmd.TryLiquidProbe( - id="command-id", - createdAt=datetime.now(), - startedAt=datetime.now(), - completedAt=datetime.now(), - key="command-key", - status=cmd.CommandStatus.SUCCEEDED, - params=cmd.TryLiquidProbeParams( - labwareId="try-liquid-probe-labware-id", - wellName="try-liquid-probe-well-name", - pipetteId="pipette-id", - ), - result=cmd.TryLiquidProbeResult( - position=DeckPoint(x=0, y=0, z=0), - z_position=0, - ), - ), - private_result=None, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="try-liquid-probe-labware-id", - well_name="try-liquid-probe-well-name", - ), - ), - ), -) -def test_movement_commands_update_current_well( - action: Union[SucceedCommandAction, FailCommandAction], - expected_location: CurrentWell, - subject: PipetteStore, -) -> None: - """It should save the last used pipette, labware, and well for movement commands.""" - load_pipette_command = create_load_pipette_command( - pipette_id="pipette-id", - pipette_name=PipetteNameType.P300_SINGLE, - mount=MountType.LEFT, - ) - - subject.handle_action( - SucceedCommandAction(private_result=None, command=load_pipette_command) - ) - subject.handle_action(action) - - assert subject.state.current_location == expected_location - - @pytest.mark.parametrize( "command", [ @@ -897,15 +776,6 @@ def test_add_pipette_config( @pytest.mark.parametrize( "action", ( - SucceedCommandAction( - command=create_touch_tip_command( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - destination=DeckPoint(x=11, y=22, z=33), - ), - private_result=None, - ), SucceedCommandAction( command=create_move_to_coordinates_command( pipette_id="pipette-id", diff --git a/robot-server/tests/runs/test_error_recovery_mapping.py b/robot-server/tests/runs/test_error_recovery_mapping.py index 21195872d39..2f16286633c 100644 --- a/robot-server/tests/runs/test_error_recovery_mapping.py +++ b/robot-server/tests/runs/test_error_recovery_mapping.py @@ -3,10 +3,7 @@ from decoy import Decoy -from opentrons.protocol_engine.commands.pipetting_common import ( - LiquidNotFoundError, - LiquidNotFoundErrorInternalData, -) +from opentrons.protocol_engine.commands.pipetting_common import LiquidNotFoundError from opentrons.protocol_engine.commands.command import ( DefinedErrorData, ) @@ -38,9 +35,7 @@ def mock_command(decoy: Decoy) -> LiquidProbe: @pytest.fixture def mock_error_data(decoy: Decoy) -> CommandDefinedErrorData: """Get a mock TipPhysicallyMissingError.""" - mock = decoy.mock( - cls=DefinedErrorData[LiquidNotFoundError, LiquidNotFoundErrorInternalData] - ) + mock = decoy.mock(cls=DefinedErrorData[LiquidNotFoundError, None]) mock_lnfe = decoy.mock(cls=LiquidNotFoundError) decoy.when(mock.public).then_return(mock_lnfe) decoy.when(mock_lnfe.errorType).then_return("liquidNotFound") From 924a2e30c1bbb76622fabf2267164e7d16835136 Mon Sep 17 00:00:00 2001 From: Max Marrone Date: Wed, 18 Sep 2024 09:56:00 -0400 Subject: [PATCH 21/24] refactor(api): Delete `DefinedErrorData.private` (#16262) ## Overview A trivial refactor as part of EXEC-652. ## Changelog The `DefinedErrorData.private` attribute was obsoleted by PR #16160's `StateUpdate` mechanism. PR #16261 deleted the last use of it, so we can delete the attribute now. `DefinedErrorData`'s sibling class, `SuccessData`, also has an identical `private` attribute. We're not quite ready to delete that one, though. --- .../protocol_engine/commands/aspirate.py | 3 +-- .../protocol_engine/commands/aspirate_in_place.py | 3 +-- .../opentrons/protocol_engine/commands/command.py | 15 ++++----------- .../protocol_engine/commands/command_unions.py | 6 +++--- .../protocol_engine/commands/dispense.py | 3 +-- .../protocol_engine/commands/dispense_in_place.py | 3 +-- .../protocol_engine/commands/liquid_probe.py | 3 +-- .../protocol_engine/commands/pick_up_tip.py | 3 +-- .../protocol_engine/commands/test_aspirate.py | 1 - .../commands/test_aspirate_in_place.py | 1 - .../protocol_engine/commands/test_dispense.py | 1 - .../commands/test_dispense_in_place.py | 1 - .../protocol_engine/commands/test_liquid_probe.py | 1 - .../protocol_engine/commands/test_pick_up_tip.py | 1 - .../execution/test_command_executor.py | 3 +-- .../tests/runs/test_error_recovery_mapping.py | 2 +- 16 files changed, 15 insertions(+), 35 deletions(-) diff --git a/api/src/opentrons/protocol_engine/commands/aspirate.py b/api/src/opentrons/protocol_engine/commands/aspirate.py index 4e5e7d52260..ac0e34424e6 100644 --- a/api/src/opentrons/protocol_engine/commands/aspirate.py +++ b/api/src/opentrons/protocol_engine/commands/aspirate.py @@ -53,7 +53,7 @@ class AspirateResult(BaseLiquidHandlingResult, DestinationPositionResult): _ExecuteReturn = Union[ SuccessData[AspirateResult, None], - DefinedErrorData[OverpressureError, None], + DefinedErrorData[OverpressureError], ] @@ -148,7 +148,6 @@ async def execute(self, params: AspirateParams) -> _ExecuteReturn: ], errorInfo={"retryLocation": (position.x, position.y, position.z)}, ), - private=None, state_update=state_update, ) else: diff --git a/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py b/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py index 7c2e5ce67fd..44dc2e93768 100644 --- a/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py +++ b/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py @@ -48,7 +48,7 @@ class AspirateInPlaceResult(BaseLiquidHandlingResult): _ExecuteReturn = Union[ SuccessData[AspirateInPlaceResult, None], - DefinedErrorData[OverpressureError, None], + DefinedErrorData[OverpressureError], ] @@ -121,7 +121,6 @@ async def execute(self, params: AspirateInPlaceParams) -> _ExecuteReturn: } ), ), - private=None, ) else: return SuccessData( diff --git a/api/src/opentrons/protocol_engine/commands/command.py b/api/src/opentrons/protocol_engine/commands/command.py index 9a519425143..759606899c0 100644 --- a/api/src/opentrons/protocol_engine/commands/command.py +++ b/api/src/opentrons/protocol_engine/commands/command.py @@ -130,7 +130,7 @@ class SuccessData(Generic[_ResultT_co, _PrivateResultT_co]): @dataclasses.dataclass(frozen=True) -class DefinedErrorData(Generic[_ErrorT_co, _PrivateResultT_co]): +class DefinedErrorData(Generic[_ErrorT_co]): """Data from a command that failed with a defined error. This should only be used for "defined" errors, not any error. @@ -140,13 +140,6 @@ class DefinedErrorData(Generic[_ErrorT_co, _PrivateResultT_co]): public: _ErrorT_co """Public error data. Exposed over HTTP and stored in databases.""" - private: _PrivateResultT_co - """Additional error data, only given to `opentrons.protocol_engine` internals. - - Deprecated: - Use `state_update` instead. - """ - state_update: StateUpdate = dataclasses.field( # todo(mm, 2024-08-22): Remove the default once all command implementations # use this, to make it harder to forget in new command implementations. @@ -246,9 +239,9 @@ class BaseCommand( object, ], DefinedErrorData[ - # Likewise, for our `error` field: + # Our _ImplementationCls must return public error data that can fit + # in our `error` field: _ErrorT, - object, ], ], ] @@ -259,7 +252,7 @@ class BaseCommand( "_ExecuteReturnT_co", bound=Union[ SuccessData[BaseModel, object], - DefinedErrorData[ErrorOccurrence, object], + DefinedErrorData[ErrorOccurrence], ], covariant=True, ) diff --git a/api/src/opentrons/protocol_engine/commands/command_unions.py b/api/src/opentrons/protocol_engine/commands/command_unions.py index ca39553e743..b586e1f50aa 100644 --- a/api/src/opentrons/protocol_engine/commands/command_unions.py +++ b/api/src/opentrons/protocol_engine/commands/command_unions.py @@ -703,9 +703,9 @@ # All `DefinedErrorData`s that implementations will actually return in practice. CommandDefinedErrorData = Union[ - DefinedErrorData[TipPhysicallyMissingError, None], - DefinedErrorData[OverpressureError, None], - DefinedErrorData[LiquidNotFoundError, None], + DefinedErrorData[TipPhysicallyMissingError], + DefinedErrorData[OverpressureError], + DefinedErrorData[LiquidNotFoundError], ] diff --git a/api/src/opentrons/protocol_engine/commands/dispense.py b/api/src/opentrons/protocol_engine/commands/dispense.py index 5ceef20f2bf..a2d0738b546 100644 --- a/api/src/opentrons/protocol_engine/commands/dispense.py +++ b/api/src/opentrons/protocol_engine/commands/dispense.py @@ -54,7 +54,7 @@ class DispenseResult(BaseLiquidHandlingResult, DestinationPositionResult): _ExecuteReturn = Union[ SuccessData[DispenseResult, None], - DefinedErrorData[OverpressureError, None], + DefinedErrorData[OverpressureError], ] @@ -111,7 +111,6 @@ async def execute(self, params: DispenseParams) -> _ExecuteReturn: ], errorInfo={"retryLocation": (position.x, position.y, position.z)}, ), - private=None, state_update=state_update, ) else: diff --git a/api/src/opentrons/protocol_engine/commands/dispense_in_place.py b/api/src/opentrons/protocol_engine/commands/dispense_in_place.py index 1553d0e8ac1..8f52af3284c 100644 --- a/api/src/opentrons/protocol_engine/commands/dispense_in_place.py +++ b/api/src/opentrons/protocol_engine/commands/dispense_in_place.py @@ -47,7 +47,7 @@ class DispenseInPlaceResult(BaseLiquidHandlingResult): _ExecuteReturn = Union[ SuccessData[DispenseInPlaceResult, None], - DefinedErrorData[OverpressureError, None], + DefinedErrorData[OverpressureError], ] @@ -99,7 +99,6 @@ async def execute(self, params: DispenseInPlaceParams) -> _ExecuteReturn: } ), ), - private=None, ) else: return SuccessData( diff --git a/api/src/opentrons/protocol_engine/commands/liquid_probe.py b/api/src/opentrons/protocol_engine/commands/liquid_probe.py index 222cf6ed608..142cd93aba4 100644 --- a/api/src/opentrons/protocol_engine/commands/liquid_probe.py +++ b/api/src/opentrons/protocol_engine/commands/liquid_probe.py @@ -81,7 +81,7 @@ class TryLiquidProbeResult(DestinationPositionResult): _LiquidProbeExecuteReturn = Union[ SuccessData[LiquidProbeResult, None], - DefinedErrorData[LiquidNotFoundError, None], + DefinedErrorData[LiquidNotFoundError], ] _TryLiquidProbeExecuteReturn = SuccessData[TryLiquidProbeResult, None] @@ -197,7 +197,6 @@ async def execute(self, params: _CommonParams) -> _LiquidProbeExecuteReturn: ) ], ), - private=None, state_update=state_update, ) else: diff --git a/api/src/opentrons/protocol_engine/commands/pick_up_tip.py b/api/src/opentrons/protocol_engine/commands/pick_up_tip.py index caa16866528..c30d2f953db 100644 --- a/api/src/opentrons/protocol_engine/commands/pick_up_tip.py +++ b/api/src/opentrons/protocol_engine/commands/pick_up_tip.py @@ -79,7 +79,7 @@ class TipPhysicallyMissingError(ErrorOccurrence): _ExecuteReturn = Union[ SuccessData[PickUpTipResult, None], - DefinedErrorData[TipPhysicallyMissingError, None], + DefinedErrorData[TipPhysicallyMissingError], ] @@ -143,7 +143,6 @@ async def execute( ) ], ), - private=None, state_update=state_update, ) else: diff --git a/api/tests/opentrons/protocol_engine/commands/test_aspirate.py b/api/tests/opentrons/protocol_engine/commands/test_aspirate.py index 0b2401bb27d..779242ccb84 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_aspirate.py +++ b/api/tests/opentrons/protocol_engine/commands/test_aspirate.py @@ -291,7 +291,6 @@ async def test_overpressure_error( wrappedErrors=[matchers.Anything()], errorInfo={"retryLocation": (position.x, position.y, position.z)}, ), - private=None, state_update=update_types.StateUpdate( pipette_location=update_types.PipetteLocationUpdate( pipette_id=pipette_id, diff --git a/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py b/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py index 77dfde9deaf..3891dd90294 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py +++ b/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py @@ -201,5 +201,4 @@ async def test_overpressure_error( wrappedErrors=[matchers.Anything()], errorInfo={"retryLocation": (position.x, position.y, position.z)}, ), - private=None, ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_dispense.py b/api/tests/opentrons/protocol_engine/commands/test_dispense.py index 6280d19f8fa..223cfcc78c9 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_dispense.py +++ b/api/tests/opentrons/protocol_engine/commands/test_dispense.py @@ -142,7 +142,6 @@ async def test_overpressure_error( wrappedErrors=[matchers.Anything()], errorInfo={"retryLocation": (position.x, position.y, position.z)}, ), - private=None, state_update=update_types.StateUpdate( pipette_location=update_types.PipetteLocationUpdate( pipette_id="pipette-id", diff --git a/api/tests/opentrons/protocol_engine/commands/test_dispense_in_place.py b/api/tests/opentrons/protocol_engine/commands/test_dispense_in_place.py index 4a6d52a635b..53a491ad211 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_dispense_in_place.py +++ b/api/tests/opentrons/protocol_engine/commands/test_dispense_in_place.py @@ -93,5 +93,4 @@ async def test_overpressure_error( wrappedErrors=[matchers.Anything()], errorInfo={"retryLocation": (position.x, position.y, position.z)}, ), - private=None, ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py b/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py index 0bbb68cd52e..bd28916239b 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py +++ b/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py @@ -215,7 +215,6 @@ async def test_liquid_not_found_error( createdAt=error_timestamp, wrappedErrors=[matchers.Anything()], ), - private=None, state_update=expected_state_update, ) else: diff --git a/api/tests/opentrons/protocol_engine/commands/test_pick_up_tip.py b/api/tests/opentrons/protocol_engine/commands/test_pick_up_tip.py index 06524b9e251..9b92fd219a0 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_pick_up_tip.py +++ b/api/tests/opentrons/protocol_engine/commands/test_pick_up_tip.py @@ -128,7 +128,6 @@ async def test_tip_physically_missing_error( public=TipPhysicallyMissingError.construct( id=error_id, createdAt=error_created_at, wrappedErrors=[matchers.Anything()] ), - private=None, state_update=update_types.StateUpdate( pipette_location=update_types.PipetteLocationUpdate( pipette_id="pipette-id", diff --git a/api/tests/opentrons/protocol_engine/execution/test_command_executor.py b/api/tests/opentrons/protocol_engine/execution/test_command_executor.py index f8f145ac2e7..2df3a2cdd25 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_command_executor.py +++ b/api/tests/opentrons/protocol_engine/execution/test_command_executor.py @@ -219,7 +219,7 @@ class _TestCommandDefinedError(ErrorOccurrence): _TestCommandReturn = Union[ SuccessData[_TestCommandResult, None], - DefinedErrorData[_TestCommandDefinedError, None], + DefinedErrorData[_TestCommandDefinedError], ] @@ -561,7 +561,6 @@ class _TestCommand( error_id = "error-id" returned_error = DefinedErrorData( public=_TestCommandDefinedError(id=error_id, createdAt=failed_at), - private=None, ) queued_command = cast( Command, diff --git a/robot-server/tests/runs/test_error_recovery_mapping.py b/robot-server/tests/runs/test_error_recovery_mapping.py index 2f16286633c..a142fbc5e30 100644 --- a/robot-server/tests/runs/test_error_recovery_mapping.py +++ b/robot-server/tests/runs/test_error_recovery_mapping.py @@ -35,7 +35,7 @@ def mock_command(decoy: Decoy) -> LiquidProbe: @pytest.fixture def mock_error_data(decoy: Decoy) -> CommandDefinedErrorData: """Get a mock TipPhysicallyMissingError.""" - mock = decoy.mock(cls=DefinedErrorData[LiquidNotFoundError, None]) + mock = decoy.mock(cls=DefinedErrorData[LiquidNotFoundError]) mock_lnfe = decoy.mock(cls=LiquidNotFoundError) decoy.when(mock.public).then_return(mock_lnfe) decoy.when(mock_lnfe.errorType).then_return("liquidNotFound") From 3947d4b62a527b1be18c5290d3525c47da176075 Mon Sep 17 00:00:00 2001 From: Jethary Alcid <66035149+jerader@users.noreply.github.com> Date: Wed, 18 Sep 2024 10:57:02 -0400 Subject: [PATCH 22/24] feat(protocol-designer): protocol steps foundation (#16252) closes AUTH-771 AUTH-817 --- components/src/atoms/MenuList/OverflowBtn.tsx | 10 +- components/src/organisms/Toolbox/index.tsx | 83 +++++--- .../localization/en/protocol_steps.json | 9 +- .../src/organisms/EditNickNameModal/index.tsx | 2 +- .../Designer/DeckSetup/DeckItemHover.tsx | 14 +- .../Designer/DeckSetup/DeckSetupContainer.tsx | 17 +- .../Designer/DeckSetup/DeckSetupDetails.tsx | 22 +- .../__tests__/DeckSetupContainer.test.tsx | 2 +- .../pages/Designer/Offdeck/OffDeckDetails.tsx | 8 +- .../src/pages/Designer/Offdeck/Offdeck.tsx | 5 +- .../Offdeck/__tests__/OffDeckDetails.test.tsx | 1 + .../Offdeck/__tests__/Offdeck.test.tsx | 2 +- .../ProtocolSteps/Timeline/AddStepButton.tsx | 196 +++++++++++++++++ .../Timeline/AddStepOverflowButton.tsx | 62 ++++++ .../Timeline/ConnectedStepInfo.tsx | 118 +++++++++++ .../ProtocolSteps/Timeline/DraggableSteps.tsx | 160 ++++++++++++++ .../ProtocolSteps/Timeline/PresavedStep.tsx | 45 ++++ .../ProtocolSteps/Timeline/StepContainer.tsx | 160 ++++++++++++++ .../Timeline/StepOverflowMenu.tsx | 109 ++++++++++ .../Timeline/TerminalItemStep.tsx | 84 ++++++++ .../Timeline/TimelineToolbox.tsx | 100 +++++++++ .../Timeline/__tests__/AddStepButton.test.tsx | 88 ++++++++ .../Timeline/__tests__/StepContainer.test.tsx | 48 +++++ .../__tests__/StepOverflowMenu.test.tsx | 40 ++++ .../__tests__/TimelineToolbox.test.tsx | 46 ++++ .../Designer/ProtocolSteps/Timeline/index.ts | 1 + .../__tests__/ProtocolSteps.test.tsx | 27 +++ .../pages/Designer/ProtocolSteps/index.tsx | 20 ++ .../Designer/__tests__/Designer.test.tsx | 9 +- .../src/pages/Designer/index.tsx | 45 ++-- protocol-designer/src/pages/Designer/types.ts | 3 + .../ProtocolOverview/UnusedModalContent.tsx | 198 ++++++++++++++++++ .../__tests__/ProtocolOverview.test.tsx | 11 - .../src/pages/ProtocolOverview/index.tsx | 94 +++++---- 34 files changed, 1704 insertions(+), 135 deletions(-) create mode 100644 protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/AddStepButton.tsx create mode 100644 protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/AddStepOverflowButton.tsx create mode 100644 protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/ConnectedStepInfo.tsx create mode 100644 protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/DraggableSteps.tsx create mode 100644 protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/PresavedStep.tsx create mode 100644 protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepContainer.tsx create mode 100644 protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepOverflowMenu.tsx create mode 100644 protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/TerminalItemStep.tsx create mode 100644 protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/TimelineToolbox.tsx create mode 100644 protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/__tests__/AddStepButton.test.tsx create mode 100644 protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/__tests__/StepContainer.test.tsx create mode 100644 protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/__tests__/StepOverflowMenu.test.tsx create mode 100644 protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/__tests__/TimelineToolbox.test.tsx create mode 100644 protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/index.ts create mode 100644 protocol-designer/src/pages/Designer/ProtocolSteps/__tests__/ProtocolSteps.test.tsx create mode 100644 protocol-designer/src/pages/Designer/ProtocolSteps/index.tsx create mode 100644 protocol-designer/src/pages/Designer/types.ts create mode 100644 protocol-designer/src/pages/ProtocolOverview/UnusedModalContent.tsx diff --git a/components/src/atoms/MenuList/OverflowBtn.tsx b/components/src/atoms/MenuList/OverflowBtn.tsx index 86e9c5c683f..ec5958746f4 100644 --- a/components/src/atoms/MenuList/OverflowBtn.tsx +++ b/components/src/atoms/MenuList/OverflowBtn.tsx @@ -5,14 +5,18 @@ import { Btn } from '../../primitives' import { BORDERS, COLORS } from '../../helix-design-system' import { SPACING } from '../../ui-style-constants' +interface OverflowBtnProps extends React.ComponentProps { + fillColor?: string +} export const OverflowBtn: ( - props: React.ComponentProps, + props: OverflowBtnProps, ref: React.ForwardedRef ) => React.ReactNode = React.forwardRef( ( - props: React.ComponentProps, + props: OverflowBtnProps, ref: React.ForwardedRef ): JSX.Element => { + const { fillColor } = props return ( diff --git a/components/src/organisms/Toolbox/index.tsx b/components/src/organisms/Toolbox/index.tsx index 49258a7fa8f..183669414bf 100644 --- a/components/src/organisms/Toolbox/index.tsx +++ b/components/src/organisms/Toolbox/index.tsx @@ -15,13 +15,16 @@ import { textDecorationUnderline } from '../../ui-style-constants/typography' export interface ToolboxProps { title: JSX.Element children: React.ReactNode - confirmButtonText: string - onConfirmClick: () => void - onCloseClick: () => void - closeButtonText: string disableCloseButton?: boolean width?: string height?: string + confirmButtonText?: string + onConfirmClick?: () => void + confirmButton?: JSX.Element + onCloseClick?: () => void + closeButtonText?: string + side?: 'left' | 'right' + horizontalSide?: 'top' | 'bottom' } export function Toolbox(props: ToolboxProps): JSX.Element { @@ -35,6 +38,9 @@ export function Toolbox(props: ToolboxProps): JSX.Element { height = '100%', disableCloseButton = false, width = '19.5rem', + confirmButton, + side = 'right', + horizontalSide = 'bottom', } = props const slideOutRef = React.useRef(null) @@ -55,12 +61,19 @@ export function Toolbox(props: ToolboxProps): JSX.Element { handleScroll() }, [slideOutRef]) + const positionStyles = { + ...(side === 'right' && { right: '0' }), + ...(side === 'left' && { left: '0' }), + ...(horizontalSide === 'bottom' && { bottom: '0' }), + ...(horizontalSide === 'top' && { top: '5rem' }), + } + return ( {title} - - - {closeButtonText} - - + {onCloseClick != null && closeButtonText != null ? ( + + + {closeButtonText} + + + ) : null} {children} - - - {confirmButtonText} - - + {onConfirmClick != null && confirmButtonText != null ? ( + + {confirmButtonText} + + ) : null} + {confirmButton != null ? confirmButton : null} + + ) : null} ) diff --git a/protocol-designer/src/assets/localization/en/protocol_steps.json b/protocol-designer/src/assets/localization/en/protocol_steps.json index e3b17ae7dfa..dc06ab1bbe4 100644 --- a/protocol-designer/src/assets/localization/en/protocol_steps.json +++ b/protocol-designer/src/assets/localization/en/protocol_steps.json @@ -1,3 +1,10 @@ { - "protocol_steps": "Protocol steps" + "delete": "Delete step", + "duplicate": "Duplicate step", + "final_deck_state": "Final deck state", + "protocol_steps": "Protocol steps", + "protocol_timeline": "Protocol timeline", + "rename": "Rename step", + "starting_deck_state": "Starting deck state", + "view_commands": "View commands" } diff --git a/protocol-designer/src/organisms/EditNickNameModal/index.tsx b/protocol-designer/src/organisms/EditNickNameModal/index.tsx index 381db38c228..ce3c54733f3 100644 --- a/protocol-designer/src/organisms/EditNickNameModal/index.tsx +++ b/protocol-designer/src/organisms/EditNickNameModal/index.tsx @@ -65,7 +65,7 @@ export function EditNickNameModal(props: EditNickNameModalProps): JSX.Element { > diff --git a/protocol-designer/src/pages/Designer/DeckSetup/DeckItemHover.tsx b/protocol-designer/src/pages/Designer/DeckSetup/DeckItemHover.tsx index f859ba452cd..c67022e7d19 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/DeckItemHover.tsx +++ b/protocol-designer/src/pages/Designer/DeckSetup/DeckItemHover.tsx @@ -16,16 +16,15 @@ import { StyledText, } from '@opentrons/components' import { getDeckSetupForActiveItem } from '../../../top-selectors/labware-locations' -import { START_TERMINAL_ITEM_ID } from '../../../steplist' import type { CoordinateTuple, DeckSlotId, Dimensions, } from '@opentrons/shared-data' -import type { TerminalItemId } from '../../../steplist' +import type { DeckSetupTabType } from '../types' -interface DeckItemHoverProps { +interface DeckItemHoverProps extends DeckSetupTabType { hover: string | null setHover: React.Dispatch> slotBoundingBox: Dimensions @@ -35,13 +34,12 @@ interface DeckItemHoverProps { setShowMenuListForId: React.Dispatch> menuListId: DeckSlotId | null isSelected?: boolean - selectedTerminalItemId?: TerminalItemId | null } export function DeckItemHover(props: DeckItemHoverProps): JSX.Element | null { const { hover, - selectedTerminalItemId, + tab, setHover, slotBoundingBox, itemId, @@ -55,11 +53,7 @@ export function DeckItemHover(props: DeckItemHoverProps): JSX.Element | null { const offDeckLabware = Object.values(deckSetup.labware).find( lw => lw.id === itemId ) - if ( - selectedTerminalItemId !== START_TERMINAL_ITEM_ID || - slotPosition === null || - isSelected - ) + if (tab === 'protocolSteps' || slotPosition === null || isSelected) return null const hoverOpacity = diff --git a/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupContainer.tsx b/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupContainer.tsx index 669bbbe0329..a552bd43a11 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupContainer.tsx +++ b/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupContainer.tsx @@ -4,6 +4,7 @@ import { ALIGN_CENTER, BORDERS, COLORS, + DIRECTION_COLUMN, DeckFromLayers, Flex, FlexTrash, @@ -24,7 +25,6 @@ import { TRASH_BIN_ADAPTER_FIXTURE, WASTE_CHUTE_CUTOUT, } from '@opentrons/shared-data' -import { getSelectedTerminalItemId } from '../../../ui/steps' import { getDeckSetupForActiveItem } from '../../../top-selectors/labware-locations' import { getDisableModuleRestrictions } from '../../../feature-flags/selectors' import { getRobotType } from '../../../file-data/selectors' @@ -32,6 +32,7 @@ import { getHasGen1MultiChannelPipette } from '../../../step-forms' import { SlotDetailsContainer } from '../../../organisms' import { selectZoomedIntoSlot } from '../../../labware-ingred/actions' import { selectors } from '../../../labware-ingred/selectors' +import { Alerts } from '../../../components/alerts/Alerts' import { DeckSetupDetails } from './DeckSetupDetails' import { animateZoom, @@ -50,6 +51,7 @@ import type { AdditionalEquipmentEntity, DeckSlot, } from '@opentrons/step-generation' +import type { DeckSetupTabType } from '../types' import type { Fixture } from './constants' const WASTE_CHUTE_SPACE = 30 @@ -65,8 +67,8 @@ const OT2_STANDARD_DECK_VIEW_LAYER_BLOCK_LIST: string[] = [ ] export const lightFill = COLORS.grey35 -export function DeckSetupContainer(): JSX.Element { - const selectedTerminalItemId = useSelector(getSelectedTerminalItemId) +export function DeckSetupContainer(props: DeckSetupTabType): JSX.Element { + const { tab } = props const activeDeckSetup = useSelector(getDeckSetupForActiveItem) const dispatch = useDispatch() const zoomIn = useSelector(selectors.getZoomedInSlot) @@ -177,7 +179,14 @@ export function DeckSetupContainer(): JSX.Element { borderRadius={BORDERS.borderRadius8} width="100%" height={zoomIn.slot != null ? '75vh' : '70vh'} + flexDirection={DIRECTION_COLUMN} > + {tab === 'protocolSteps' ? ( + + {/* TODO: update the alerts to match latest designs */} + + + ) : null} areas.location as CutoutId )} diff --git a/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupDetails.tsx b/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupDetails.tsx index d1e6b435177..5b68765b881 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupDetails.tsx +++ b/protocol-designer/src/pages/Designer/DeckSetup/DeckSetupDetails.tsx @@ -43,10 +43,10 @@ import type { LabwareOnDeck as LabwareOnDeckType, ModuleOnDeck, } from '../../../step-forms' -import type { TerminalItemId } from '../../../steplist' +import type { DeckSetupTabType } from '../types' import type { Fixture } from './constants' -interface DeckSetupDetailsProps { +interface DeckSetupDetailsProps extends DeckSetupTabType { activeDeckSetup: InitialDeckSetup addEquipment: (slotId: string) => void deckDef: DeckDefinition @@ -57,7 +57,6 @@ interface DeckSetupDetailsProps { setHover: React.Dispatch> showGen1MultichannelCollisionWarnings: boolean stagingAreaCutoutIds: CutoutId[] - selectedTerminalItemId?: TerminalItemId | null selectedZoomInSlot?: DeckSlotId } @@ -70,8 +69,8 @@ export const DeckSetupDetails = (props: DeckSetupDetailsProps): JSX.Element => { hoveredFixture, hoveredLabware, hoveredModule, - selectedTerminalItemId, selectedZoomInSlot, + tab, setHover, showGen1MultichannelCollisionWarnings, stagingAreaCutoutIds, @@ -149,10 +148,7 @@ export const DeckSetupDetails = (props: DeckSetupDetailsProps): JSX.Element => { ): React.ComponentProps['innerProps'] => { if (moduleState.type === THERMOCYCLER_MODULE_TYPE) { let lidMotorState = 'unknown' - if ( - selectedTerminalItemId === '__initial_setup__' || - moduleState.lidOpen === true - ) { + if (tab === 'startingDeck' || moduleState.lidOpen) { lidMotorState = 'open' } else if (moduleState.lidOpen === false) { lidMotorState = 'closed' @@ -218,7 +214,7 @@ export const DeckSetupDetails = (props: DeckSetupDetailsProps): JSX.Element => { slotBoundingBox={controlSelectDimensions} slotPosition={[0, 0, 0]} itemId={slotId} - selectedTerminalItemId={props.selectedTerminalItemId} + tab={tab} /> ) : null} @@ -233,7 +229,7 @@ export const DeckSetupDetails = (props: DeckSetupDetailsProps): JSX.Element => { slotBoundingBox={labwareInterfaceBoundingBox} slotPosition={[0, 0, 0]} itemId={slotId} - selectedTerminalItemId={props.selectedTerminalItemId} + tab={tab} /> ) : null} @@ -288,7 +284,7 @@ export const DeckSetupDetails = (props: DeckSetupDetailsProps): JSX.Element => { deckDef )} itemId={addressableArea.id} - selectedTerminalItemId={props.selectedTerminalItemId} + tab={tab} /> ) @@ -327,7 +323,7 @@ export const DeckSetupDetails = (props: DeckSetupDetailsProps): JSX.Element => { slotBoundingBox={slotBoundingBox} slotPosition={slotPosition} itemId={labware.slot} - selectedTerminalItemId={props.selectedTerminalItemId} + tab={tab} /> ) : null @@ -389,7 +385,7 @@ export const DeckSetupDetails = (props: DeckSetupDetailsProps): JSX.Element => { slotBoundingBox={slotBoundingBox} slotPosition={slotPosition} itemId={slotOnDeck ?? ''} - selectedTerminalItemId={props.selectedTerminalItemId} + tab={tab} /> ) diff --git a/protocol-designer/src/pages/Designer/DeckSetup/__tests__/DeckSetupContainer.test.tsx b/protocol-designer/src/pages/Designer/DeckSetup/__tests__/DeckSetupContainer.test.tsx index 86c45b00d62..e0a10672039 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/__tests__/DeckSetupContainer.test.tsx +++ b/protocol-designer/src/pages/Designer/DeckSetup/__tests__/DeckSetupContainer.test.tsx @@ -33,7 +33,7 @@ vi.mock('@opentrons/components', async importOriginal => { }) const render = () => { - return renderWithProviders()[0] + return renderWithProviders()[0] } describe('DeckSetupContainer', () => { diff --git a/protocol-designer/src/pages/Designer/Offdeck/OffDeckDetails.tsx b/protocol-designer/src/pages/Designer/Offdeck/OffDeckDetails.tsx index 9dbddefbfba..08c3b7c780a 100644 --- a/protocol-designer/src/pages/Designer/Offdeck/OffDeckDetails.tsx +++ b/protocol-designer/src/pages/Designer/Offdeck/OffDeckDetails.tsx @@ -18,19 +18,19 @@ import { import * as wellContentsSelectors from '../../../top-selectors/well-contents' import { wellFillFromWellContents } from '../../../components/labware' import { selectors } from '../../../labware-ingred/selectors' -import { START_TERMINAL_ITEM_ID } from '../../../steplist' import { getDeckSetupForActiveItem } from '../../../top-selectors/labware-locations' import { DeckItemHover } from '../DeckSetup/DeckItemHover' import { SlotDetailsContainer } from '../../../organisms' import { getRobotType } from '../../../file-data/selectors' import { SlotOverflowMenu } from '../DeckSetup/SlotOverflowMenu' import type { DeckSlotId } from '@opentrons/shared-data' +import type { DeckSetupTabType } from '../types' -interface OffDeckDetailsProps { +interface OffDeckDetailsProps extends DeckSetupTabType { addLabware: () => void } export function OffDeckDetails(props: OffDeckDetailsProps): JSX.Element { - const { addLabware } = props + const { addLabware, tab } = props const { t, i18n } = useTranslation('starting_deck_state') const [hoverSlot, setHoverSlot] = React.useState(null) const [menuListId, setShowMenuListForId] = React.useState( @@ -124,7 +124,7 @@ export function OffDeckDetails(props: OffDeckDetailsProps): JSX.Element { slotBoundingBox={xyzDimensions} slotPosition={[0, 0, 0]} itemId={lw.id} - selectedTerminalItemId={START_TERMINAL_ITEM_ID} + tab={tab} /> )} diff --git a/protocol-designer/src/pages/Designer/Offdeck/Offdeck.tsx b/protocol-designer/src/pages/Designer/Offdeck/Offdeck.tsx index f8dad4ba4ea..bc669a3cd37 100644 --- a/protocol-designer/src/pages/Designer/Offdeck/Offdeck.tsx +++ b/protocol-designer/src/pages/Designer/Offdeck/Offdeck.tsx @@ -22,11 +22,13 @@ import { selectZoomedIntoSlot } from '../../../labware-ingred/actions' import { DeckSetupTools } from '../DeckSetup/DeckSetupTools' import { LabwareLabel } from '../LabwareLabel' import { OffDeckDetails } from './OffDeckDetails' +import type { DeckSetupTabType } from '../types' const STANDARD_X_WIDTH = '127.76px' const STANDARD_Y_HEIGHT = '85.48px' -export function OffDeck(): JSX.Element { +export function OffDeck(props: DeckSetupTabType): JSX.Element { + const { tab } = props const { t, i18n } = useTranslation('starting_deck_state') const [hoveredLabware, setHoveredLabware] = React.useState( null @@ -159,6 +161,7 @@ export function OffDeck(): JSX.Element { ) : ( { dispatch(selectZoomedIntoSlot({ slot: 'offDeck', cutout: null })) }} diff --git a/protocol-designer/src/pages/Designer/Offdeck/__tests__/OffDeckDetails.test.tsx b/protocol-designer/src/pages/Designer/Offdeck/__tests__/OffDeckDetails.test.tsx index 1e5f932e17c..f3c43ccd436 100644 --- a/protocol-designer/src/pages/Designer/Offdeck/__tests__/OffDeckDetails.test.tsx +++ b/protocol-designer/src/pages/Designer/Offdeck/__tests__/OffDeckDetails.test.tsx @@ -36,6 +36,7 @@ describe('OffDeckDetails', () => { beforeEach(() => { props = { + tab: 'startingDeck', addLabware: vi.fn(), } vi.mocked(getRobotType).mockReturnValue(FLEX_ROBOT_TYPE) diff --git a/protocol-designer/src/pages/Designer/Offdeck/__tests__/Offdeck.test.tsx b/protocol-designer/src/pages/Designer/Offdeck/__tests__/Offdeck.test.tsx index 167cf7437ae..b6a3a038239 100644 --- a/protocol-designer/src/pages/Designer/Offdeck/__tests__/Offdeck.test.tsx +++ b/protocol-designer/src/pages/Designer/Offdeck/__tests__/Offdeck.test.tsx @@ -21,7 +21,7 @@ vi.mock('@opentrons/components', async importOriginal => { }) const render = () => { - return renderWithProviders()[0] + return renderWithProviders() } describe('OffDeck', () => { diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/AddStepButton.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/AddStepButton.tsx new file mode 100644 index 00000000000..b7e070de5c5 --- /dev/null +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/AddStepButton.tsx @@ -0,0 +1,196 @@ +import * as React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { createPortal } from 'react-dom' +import { useTranslation } from 'react-i18next' +import { + useHoverTooltip, + TOOLTIP_TOP, + TOOLTIP_FIXED, + Tooltip, + COLORS, + DIRECTION_COLUMN, + Flex, + POSITION_ABSOLUTE, + BORDERS, + NO_WRAP, + useOnClickOutside, + SecondaryButton, +} from '@opentrons/components' +import { + HEATERSHAKER_MODULE_TYPE, + MAGNETIC_MODULE_TYPE, + TEMPERATURE_MODULE_TYPE, + THERMOCYCLER_MODULE_TYPE, +} from '@opentrons/shared-data' +import { + actions as stepsActions, + getIsMultiSelectMode, +} from '../../../../ui/steps' +import { + selectors as stepFormSelectors, + getIsModuleOnDeck, +} from '../../../../step-forms' +import { getEnableComment } from '../../../../feature-flags/selectors' +import { getMainPagePortalEl } from '../../../../components/portals/MainPageModalPortal' +import { + CLOSE_UNSAVED_STEP_FORM, + ConfirmDeleteModal, +} from '../../../../components/modals/ConfirmDeleteModal' +import { AddStepOverflowButton } from './AddStepOverflowButton' + +import type { ThunkDispatch } from 'redux-thunk' +import type { BaseState } from '../../../../types' +import type { StepType } from '../../../../form-types' + +export function AddStepButton(): JSX.Element { + const { t } = useTranslation(['tooltip', 'button']) + const enableComment = useSelector(getEnableComment) + const dispatch = useDispatch>() + const [targetProps, tooltipProps] = useHoverTooltip({ + placement: TOOLTIP_TOP, + strategy: TOOLTIP_FIXED, + }) + const currentFormIsPresaved = useSelector( + stepFormSelectors.getCurrentFormIsPresaved + ) + const formHasChanges = useSelector( + stepFormSelectors.getCurrentFormHasUnsavedChanges + ) + const isStepCreationDisabled = useSelector(getIsMultiSelectMode) + const modules = useSelector(stepFormSelectors.getInitialDeckSetup).modules + const [ + showStepOverflowMenu, + setShowStepOverflowMenu, + ] = React.useState(false) + const overflowWrapperRef = useOnClickOutside({ + onClickOutside: () => { + setShowStepOverflowMenu(false) + }, + }) + const [ + enqueuedStepType, + setEnqueuedStepType, + ] = React.useState(null) + + const getSupportedSteps = (): Array< + Exclude + > => + enableComment + ? [ + 'comment', + 'moveLabware', + 'moveLiquid', + 'mix', + 'pause', + 'heaterShaker', + 'magnet', + 'temperature', + 'thermocycler', + ] + : [ + 'moveLabware', + 'moveLiquid', + 'mix', + 'pause', + 'heaterShaker', + 'magnet', + 'temperature', + 'thermocycler', + ] + const isStepTypeEnabled: Record< + Exclude, + boolean + > = { + comment: enableComment, + moveLabware: true, + moveLiquid: true, + mix: true, + pause: true, + magnet: getIsModuleOnDeck(modules, MAGNETIC_MODULE_TYPE), + temperature: getIsModuleOnDeck(modules, TEMPERATURE_MODULE_TYPE), + thermocycler: getIsModuleOnDeck(modules, THERMOCYCLER_MODULE_TYPE), + heaterShaker: getIsModuleOnDeck(modules, HEATERSHAKER_MODULE_TYPE), + } + + const addStep = ( + stepType: StepType + ): ReturnType => + dispatch(stepsActions.addAndSelectStepWithHints({ stepType })) + + const items = getSupportedSteps() + .filter(stepType => isStepTypeEnabled[stepType]) + .map(stepType => ( + { + if (currentFormIsPresaved || formHasChanges) { + setEnqueuedStepType(stepType) + } else { + addStep(stepType) + } + setShowStepOverflowMenu(false) + }} + /> + )) + + return ( + <> + {/* TODO(ja): update this modal to match latest modal designs */} + {enqueuedStepType !== null && + createPortal( + { + setEnqueuedStepType(null) + }} + onContinueClick={() => { + if (enqueuedStepType !== null) { + addStep(enqueuedStepType) + setEnqueuedStepType(null) + } + }} + />, + getMainPagePortalEl() + )} + + {showStepOverflowMenu ? ( + { + e.preventDefault() + e.stopPropagation() + }} + > + {items} + + ) : null} + + {isStepCreationDisabled && ( + + {t(`disabled_step_creation`)} + + )} + { + setShowStepOverflowMenu(true) + }} + disabled={isStepCreationDisabled} + > + {t('button:add_step')} + + + ) +} diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/AddStepOverflowButton.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/AddStepOverflowButton.tsx new file mode 100644 index 00000000000..730dc843814 --- /dev/null +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/AddStepOverflowButton.tsx @@ -0,0 +1,62 @@ +import * as React from 'react' +import { useTranslation } from 'react-i18next' +import styled from 'styled-components' +import { + ALIGN_CENTER, + COLORS, + Icon, + SPACING, + StyledText, +} from '@opentrons/components' +import { stepIconsByType } from '../../../../form-types' +import type { StepType } from '../../../../form-types' + +export interface AddStepOverflowButtonProps { + onClick: () => void + stepType: StepType +} + +export function AddStepOverflowButton( + props: AddStepOverflowButtonProps +): JSX.Element { + const { onClick, stepType } = props + const { t, i18n } = useTranslation(['tooltip', 'application']) + // TODO(ja): add or delete tooltips when designs are finalized + // const [targetProps, tooltipProps] = useHoverTooltip({ + // placement: TOOLTIP_RIGHT, + // }) + // const tooltipMessage = t(`step_description.${stepType}`) + return ( + <> + + + + {i18n.format( + t(`application:stepType.${stepType}`, stepType), + 'capitalize' + )} + + + {/* {tooltipMessage} */} + + ) +} + +const MenuButton = styled.button` + background-color: ${COLORS.transparent}; + align-items: ${ALIGN_CENTER}; + grid-gap: ${SPACING.spacing8}; + width: 100%; + cursor: pointer; + padding: ${SPACING.spacing8} ${SPACING.spacing12}; + border: none; + border-radius: inherit; + display: flex; + &:hover { + background-color: ${COLORS.blue10}; + } + &:disabled { + color: ${COLORS.grey40}; + cursor: auto; + } +` diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/ConnectedStepInfo.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/ConnectedStepInfo.tsx new file mode 100644 index 00000000000..303cc347592 --- /dev/null +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/ConnectedStepInfo.tsx @@ -0,0 +1,118 @@ +import * as React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { useTranslation } from 'react-i18next' +import { COLORS, useConditionalConfirm } from '@opentrons/components' +import * as timelineWarningSelectors from '../../../../top-selectors/timelineWarnings' +import { selectors as dismissSelectors } from '../../../../dismiss' +import { selectors as stepFormSelectors } from '../../../../step-forms' +import { + actions as stepsActions, + getHoveredStepId, + getHoveredSubstep, + getMultiSelectItemIds, + getSelectedStepId, +} from '../../../../ui/steps' +import { selectors as fileDataSelectors } from '../../../../file-data' +import { + CLOSE_STEP_FORM_WITH_CHANGES, + CLOSE_UNSAVED_STEP_FORM, + ConfirmDeleteModal, +} from '../../../../components/modals/ConfirmDeleteModal' +import { stepIconsByType } from '../../../../form-types' +import { StepContainer } from './StepContainer' + +import type { ThunkDispatch } from 'redux-thunk' +import type { HoverOnStepAction } from '../../../../ui/steps' +import type { DeleteModalType } from '../../../../components/modals/ConfirmDeleteModal' +import type { StepIdType } from '../../../../form-types' +import type { BaseState, ThunkAction } from '../../../../types' + +export interface ConnectedStepInfoProps { + stepId: StepIdType + stepNumber: number +} + +export function ConnectedStepInfo(props: ConnectedStepInfoProps): JSX.Element { + const { stepId, stepNumber } = props + const { t } = useTranslation('application') + const dispatch = useDispatch>() + const step = useSelector(stepFormSelectors.getSavedStepForms)[stepId] + const argsAndErrors = useSelector(stepFormSelectors.getArgsAndErrorsByStepId)[ + stepId + ] + const errorStepId = useSelector(fileDataSelectors.getErrorStepId) + const hasError = errorStepId === stepId || argsAndErrors.errors != null + const hasTimelineWarningsPerStep = useSelector( + timelineWarningSelectors.getHasTimelineWarningsPerStep + ) + const hasFormLevelWarningsPerStep = useSelector( + dismissSelectors.getHasFormLevelWarningsPerStep + ) + + const hasWarnings = + hasTimelineWarningsPerStep[stepId] || hasFormLevelWarningsPerStep[stepId] + const hoveredSubstep = useSelector(getHoveredSubstep) + const hoveredStep = useSelector(getHoveredStepId) + const selectedStepId = useSelector(getSelectedStepId) + const multiSelectItemIds = useSelector(getMultiSelectItemIds) + const selected: boolean = multiSelectItemIds?.length + ? multiSelectItemIds.includes(stepId) + : selectedStepId === stepId + + const currentFormIsPresaved = useSelector( + stepFormSelectors.getCurrentFormIsPresaved + ) + const singleEditFormHasUnsavedChanges = useSelector( + stepFormSelectors.getCurrentFormHasUnsavedChanges + ) + + const selectStep = (): ThunkAction => + dispatch(stepsActions.selectStep(stepId)) + const highlightStep = (): HoverOnStepAction => + dispatch(stepsActions.hoverOnStep(stepId)) + const unhighlightStep = (): HoverOnStepAction => + dispatch(stepsActions.hoverOnStep(null)) + + const handleStepItemSelection = (): void => { + selectStep() + } + + const { confirm, showConfirmation, cancel } = useConditionalConfirm( + handleStepItemSelection, + currentFormIsPresaved || singleEditFormHasUnsavedChanges + ) + + const getModalType = (): DeleteModalType => { + if (currentFormIsPresaved) { + return CLOSE_UNSAVED_STEP_FORM + } else { + return CLOSE_STEP_FORM_WITH_CHANGES + } + } + const iconName = stepIconsByType[step.stepType] + + return ( + <> + {showConfirmation && ( + + )} + + + ) +} diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/DraggableSteps.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/DraggableSteps.tsx new file mode 100644 index 00000000000..888bee4b8f2 --- /dev/null +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/DraggableSteps.tsx @@ -0,0 +1,160 @@ +import * as React from 'react' +import { useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' +import { useDrop, useDrag } from 'react-dnd' +import { + Box, + COLORS, + DIRECTION_COLUMN, + Flex, + SPACING, +} from '@opentrons/components' +import { DND_TYPES } from '../../../../constants' +import { selectors as stepFormSelectors } from '../../../../step-forms' +import { stepIconsByType } from '../../../../form-types' +import { StepContainer } from './StepContainer' +import { ConnectedStepInfo } from './ConnectedStepInfo' +import type { DragLayerMonitor, DropTargetOptions } from 'react-dnd' +import type { StepIdType } from '../../../../form-types' +import type { ConnectedStepItemProps } from '../../../../containers/ConnectedStepItem' + +interface DragDropStepProps extends ConnectedStepItemProps { + stepId: StepIdType + moveStep: (stepId: StepIdType, value: number) => void + findStepIndex: (stepId: StepIdType) => number + orderedStepIds: string[] +} + +interface DropType { + stepId: StepIdType +} + +function DragDropStep(props: DragDropStepProps): JSX.Element { + const { stepId, moveStep, findStepIndex, orderedStepIds, stepNumber } = props + const stepRef = React.useRef(null) + + const [{ isDragging }, drag] = useDrag( + () => ({ + type: DND_TYPES.STEP_ITEM, + item: { stepId }, + collect: (monitor: DragLayerMonitor) => ({ + isDragging: monitor.isDragging(), + }), + }), + [orderedStepIds] + ) + + const [{ handlerId }, drop] = useDrop( + () => ({ + accept: DND_TYPES.STEP_ITEM, + canDrop: () => { + return true + }, + drop: (item: DropType) => { + const draggedId = item.stepId + if (draggedId !== stepId) { + const overIndex = findStepIndex(stepId) + moveStep(draggedId, overIndex) + } + }, + collect: (monitor: DropTargetOptions) => ({ + handlerId: monitor.getHandlerId(), + }), + }), + [orderedStepIds] + ) + + drag(drop(stepRef)) + return ( + + + + ) +} + +interface DraggableStepsProps { + orderedStepIds: StepIdType[] + reorderSteps: (steps: StepIdType[]) => void +} +export function DraggableSteps(props: DraggableStepsProps): JSX.Element | null { + const { orderedStepIds, reorderSteps } = props + const { t } = useTranslation('shared') + + const findStepIndex = (stepId: StepIdType): number => + orderedStepIds.findIndex(id => stepId === id) + + const moveStep = (stepId: StepIdType, targetIndex: number): void => { + const currentIndex = findStepIndex(stepId) + + const currentRemoved = [ + ...orderedStepIds.slice(0, currentIndex), + ...orderedStepIds.slice(currentIndex + 1, orderedStepIds.length), + ] + const currentReinserted = [ + ...currentRemoved.slice(0, targetIndex), + stepId, + ...currentRemoved.slice(targetIndex, currentRemoved.length), + ] + if (confirm(t('confirm_reorder') as string)) { + reorderSteps(currentReinserted) + } + } + + return ( + + {orderedStepIds.map((stepId: StepIdType, index: number) => ( + + ))} + + + ) +} + +function StepDragPreview(): JSX.Element | null { + const [{ isDragging, itemType, item, currentOffset }] = useDrag(() => ({ + type: DND_TYPES.STEP_ITEM, + collect: (monitor: DragLayerMonitor) => ({ + currentOffset: monitor.getSourceClientOffset(), + isDragging: monitor.isDragging(), + itemType: monitor.getItemType(), + item: monitor.getItem() as { stepId: StepIdType }, + }), + })) + + const savedStepForms = useSelector(stepFormSelectors.getSavedStepForms) + const savedForm = item && savedStepForms[item.stepId] + const { stepType, stepName } = savedForm || {} + + if ( + itemType !== DND_TYPES.STEP_ITEM || + !isDragging || + stepType == null || + currentOffset == null + ) { + return null + } + + return ( + + + + ) +} diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/PresavedStep.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/PresavedStep.tsx new file mode 100644 index 00000000000..dad2855e80a --- /dev/null +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/PresavedStep.tsx @@ -0,0 +1,45 @@ +import * as React from 'react' +import { useSelector, useDispatch } from 'react-redux' +import { useTranslation } from 'react-i18next' +import { PRESAVED_STEP_ID } from '../../../../steplist/types' +import { selectors as stepFormSelectors } from '../../../../step-forms' +import { stepIconsByType } from '../../../../form-types' +import { + getHoveredTerminalItemId, + getSelectedTerminalItemId, + actions as stepsActions, +} from '../../../../ui/steps' +import { StepContainer } from './StepContainer' + +export function PresavedStep(): JSX.Element | null { + const { t } = useTranslation('application') + const presavedStepForm = useSelector(stepFormSelectors.getPresavedStepForm) + const stepNumber = useSelector(stepFormSelectors.getOrderedStepIds).length + 1 + const hovered = useSelector(getHoveredTerminalItemId) === PRESAVED_STEP_ID + const selected = useSelector(getSelectedTerminalItemId) === PRESAVED_STEP_ID + const dispatch = useDispatch() + + if (presavedStepForm === null) { + return null + } + + const highlightStep = (): void => { + dispatch(stepsActions.hoverOnTerminalItem(PRESAVED_STEP_ID)) + } + const unhighlightStep = (): void => { + dispatch(stepsActions.hoverOnTerminalItem(null)) + } + + const stepType = presavedStepForm.stepType + + return ( + + ) +} diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepContainer.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepContainer.tsx new file mode 100644 index 00000000000..e945ad3f64f --- /dev/null +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepContainer.tsx @@ -0,0 +1,160 @@ +import * as React from 'react' +import { useSelector } from 'react-redux' +import { createPortal } from 'react-dom' +import { + ALIGN_CENTER, + BORDERS, + Box, + Btn, + COLORS, + Flex, + Icon, + JUSTIFY_CENTER, + JUSTIFY_SPACE_BETWEEN, + JUSTIFY_START, + OverflowBtn, + SPACING, + StyledText, +} from '@opentrons/components' +import { getUnsavedForm } from '../../../../step-forms/selectors' +import { getTopPortalEl } from '../../../../components/portals/TopPortal' +import { StepOverflowMenu } from './StepOverflowMenu' + +import type { IconName } from '@opentrons/components' + +const STARTING_DECK_STATE = 'Starting deck state' +const FINAL_DECK_STATE = 'Final deck state' + +export interface StepContainerProps { + title: string + iconName: IconName + stepId?: string + iconColor?: string + onClick?: (event: React.MouseEvent) => void + onMouseEnter?: (event: React.MouseEvent) => void + onMouseLeave?: (event: React.MouseEvent) => void + selected?: boolean + hovered?: boolean +} + +export function StepContainer(props: StepContainerProps): JSX.Element { + const { + stepId, + iconName, + onMouseEnter, + onMouseLeave, + selected, + onClick, + hovered, + iconColor, + title, + } = props + const formData = useSelector(getUnsavedForm) + const [top, setTop] = React.useState(0) + const menuRootRef = React.useRef(null) + const [stepOverflowMenu, setStepOverflowMenu] = React.useState(false) + const isStartingOrEndingState = + title === STARTING_DECK_STATE || title === FINAL_DECK_STATE + + let backgroundColor = isStartingOrEndingState ? COLORS.blue20 : COLORS.grey20 + let color = COLORS.black90 + if (selected) { + backgroundColor = COLORS.blue50 + color = COLORS.white + } + if (hovered && !selected) { + backgroundColor = COLORS.blue30 + color = COLORS.black90 + } + + const handleOverflowClick = (event: React.MouseEvent): void => { + const { clientY } = event + + const screenHeight = window.innerHeight + const rootHeight = menuRootRef.current + ? menuRootRef.current.offsetHeight + : 0 + const top = + screenHeight - clientY > rootHeight + ? clientY + 5 + : clientY - rootHeight - 5 + + setTop(top) + } + + React.useEffect(() => { + global.addEventListener('click', handleClick) + return () => { + global.removeEventListener('click', handleClick) + } + }) + + const handleClick = (event: MouseEvent): void => { + const wasOutside = !( + event.target instanceof Node && + menuRootRef.current?.contains(event.target) + ) + + if (wasOutside && stepOverflowMenu) { + setStepOverflowMenu(false) + } + } + return ( + <> + + + + + {iconName && ( + + )} + {formData != null ? null : ( + + {title} + + )} + + {hovered && !isStartingOrEndingState && formData == null ? ( + { + e.preventDefault() + e.stopPropagation() + setStepOverflowMenu(prev => !prev) + handleOverflowClick(e) + }} + /> + ) : null} + + + + {stepOverflowMenu && stepId != null + ? createPortal( + , + getTopPortalEl() + ) + : null} + + ) +} diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepOverflowMenu.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepOverflowMenu.tsx new file mode 100644 index 00000000000..8807f54c6af --- /dev/null +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepOverflowMenu.tsx @@ -0,0 +1,109 @@ +import * as React from 'react' +import { useTranslation } from 'react-i18next' +import styled from 'styled-components' +import { useDispatch } from 'react-redux' + +import { + ALIGN_CENTER, + BORDERS, + COLORS, + DIRECTION_COLUMN, + Divider, + Flex, + NO_WRAP, + POSITION_ABSOLUTE, + SPACING, +} from '@opentrons/components' +import { actions as steplistActions } from '../../../../steplist' +import { actions as stepsActions } from '../../../../ui/steps' + +import type { ThunkDispatch } from 'redux-thunk' +import type { BaseState } from '../../../../types' +import type { StepIdType } from '../../../../form-types' + +interface StepOverflowMenuProps { + stepId: string + menuRootRef: React.MutableRefObject + top: number +} + +export function StepOverflowMenu(props: StepOverflowMenuProps): JSX.Element { + const { stepId, menuRootRef, top } = props + const { t } = useTranslation('protocol_steps') + const dispatch = useDispatch>() + const deleteStep = (stepId: StepIdType): void => { + dispatch(steplistActions.deleteStep(stepId)) + } + const duplicateStep = ( + stepId: StepIdType + ): ReturnType => + dispatch(stepsActions.duplicateStep(stepId)) + + return ( + { + e.preventDefault() + e.stopPropagation() + }} + > + { + console.log('wire this up') + }} + > + {t('rename')} + + { + console.log('wire this up') + }} + > + {t('view_commands')} + + { + duplicateStep(stepId) + }} + > + {t('duplicate')} + + + { + deleteStep(stepId) + }} + > + {t('delete')} + + + ) +} + +const MenuButton = styled.button` + background-color: ${COLORS.transparent}; + align-items: ${ALIGN_CENTER}; + grid-gap: ${SPACING.spacing8}; + width: 100%; + cursor: pointer; + padding: ${SPACING.spacing8} ${SPACING.spacing12}; + border: none; + border-radius: inherit; + display: flex; + &:hover { + background-color: ${COLORS.blue10}; + } + &:disabled { + color: ${COLORS.grey40}; + cursor: auto; + } +` diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/TerminalItemStep.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/TerminalItemStep.tsx new file mode 100644 index 00000000000..8f1fbc0dd9a --- /dev/null +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/TerminalItemStep.tsx @@ -0,0 +1,84 @@ +import * as React from 'react' +import { useSelector, useDispatch } from 'react-redux' +import { useConditionalConfirm } from '@opentrons/components' +import { + getHoveredTerminalItemId, + getSelectedTerminalItemId, + getIsMultiSelectMode, + actions as stepsActions, +} from '../../../../ui/steps' +import { + getCurrentFormIsPresaved, + getCurrentFormHasUnsavedChanges, +} from '../../../../step-forms/selectors' +import { + CLOSE_STEP_FORM_WITH_CHANGES, + CLOSE_UNSAVED_STEP_FORM, + ConfirmDeleteModal, +} from '../../../../components/modals/ConfirmDeleteModal' +import { StepContainer } from './StepContainer' + +import type { + SelectTerminalItemAction, + HoverOnTerminalItemAction, +} from '../../../../ui/steps' +import type { TerminalItemId } from '../../../../steplist' + +export interface TerminalItemStepProps { + id: TerminalItemId + title: string +} + +export function TerminalItemStep(props: TerminalItemStepProps): JSX.Element { + const { id, title } = props + const hovered = useSelector(getHoveredTerminalItemId) === id + const selected = useSelector(getSelectedTerminalItemId) === id + const currentFormIsPresaved = useSelector(getCurrentFormIsPresaved) + const formHasChanges = useSelector(getCurrentFormHasUnsavedChanges) + const isMultiSelectMode = useSelector(getIsMultiSelectMode) + + const dispatch = useDispatch() + + const selectItem = (): SelectTerminalItemAction => + dispatch(stepsActions.selectTerminalItem(id)) + + const onMouseEnter = (): HoverOnTerminalItemAction => + dispatch(stepsActions.hoverOnTerminalItem(id)) + const onMouseLeave = (): HoverOnTerminalItemAction => + dispatch(stepsActions.hoverOnTerminalItem(null)) + + const { confirm, showConfirmation, cancel } = useConditionalConfirm( + selectItem, + currentFormIsPresaved || formHasChanges + ) + + const onClick = isMultiSelectMode ? () => null : confirm + + return ( + <> + {showConfirmation && ( + + )} + + + ) +} diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/TimelineToolbox.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/TimelineToolbox.tsx new file mode 100644 index 00000000000..377e5eb3984 --- /dev/null +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/TimelineToolbox.tsx @@ -0,0 +1,100 @@ +import * as React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { useTranslation } from 'react-i18next' +import { + DIRECTION_COLUMN, + FLEX_MAX_CONTENT, + Flex, + SPACING, + StyledText, + Toolbox, +} from '@opentrons/components' +import { + END_TERMINAL_ITEM_ID, + START_TERMINAL_ITEM_ID, + actions as steplistActions, +} from '../../../../steplist' +import { actions as stepsActions } from '../../../../ui/steps' +import { selectors as stepFormSelectors } from '../../../../step-forms' +import { getUnsavedForm } from '../../../../step-forms/selectors' +import { TerminalItemStep } from './TerminalItemStep' +import { AddStepButton } from './AddStepButton' +import { PresavedStep } from './PresavedStep' +import { DraggableSteps } from './DraggableSteps' + +import type { StepIdType } from '../../../../form-types' +import type { ThunkDispatch } from '../../../../types' + +export const TimelineToolbox = (): JSX.Element => { + const { t } = useTranslation('protocol_steps') + const orderedStepIds = useSelector(stepFormSelectors.getOrderedStepIds) + const formData = useSelector(getUnsavedForm) + // TODO: potentially add batch edit capabilities back in + // const isMultiSelectMode = useSelector(getIsMultiSelectMode) + const dispatch = useDispatch>() + + const handleKeyDown: (e: KeyboardEvent) => void = e => { + const { key, altKey: altIsPressed } = e + + if (altIsPressed) { + let delta = 0 + if (key === 'ArrowUp') { + delta = -1 + } else if (key === 'ArrowDown') { + delta = 1 + } + dispatch(stepsActions.reorderSelectedStep(delta)) + } + } + + React.useEffect(() => { + const onKeyDown = (e: KeyboardEvent): void => { + handleKeyDown(e) + } + + global.addEventListener('keydown', onKeyDown, false) + + return () => { + global.removeEventListener('keydown', onKeyDown, false) + } + }, []) + + return ( + + {t('protocol_timeline')} + + } + confirmButton={formData != null ? undefined : } + > + {/* todo(ja): this is for batch edit which we will need to add back in */} + {/* */} + + + { + dispatch(steplistActions.reorderSteps(stepIds)) + }} + /> + + + + + ) +} diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/__tests__/AddStepButton.test.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/__tests__/AddStepButton.test.tsx new file mode 100644 index 00000000000..43b386fb20d --- /dev/null +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/__tests__/AddStepButton.test.tsx @@ -0,0 +1,88 @@ +import * as React from 'react' +import { describe, it, vi, beforeEach } from 'vitest' +import { fireEvent, screen } from '@testing-library/react' +import { + HEATERSHAKER_MODULE_TYPE, + HEATERSHAKER_MODULE_V1, + MAGNETIC_MODULE_TYPE, + MAGNETIC_MODULE_V1, + TEMPERATURE_MODULE_TYPE, + TEMPERATURE_MODULE_V1, + THERMOCYCLER_MODULE_TYPE, + THERMOCYCLER_MODULE_V1, +} from '@opentrons/shared-data' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { i18n } from '../../../../../assets/localization' +import { AddStepButton } from '../AddStepButton' +import { getEnableComment } from '../../../../../feature-flags/selectors' +import { + getCurrentFormIsPresaved, + getInitialDeckSetup, +} from '../../../../../step-forms/selectors' +import { getIsMultiSelectMode } from '../../../../../ui/steps' + +vi.mock('../../../../../feature-flags/selectors') +vi.mock('../../../../../ui/steps') +vi.mock('../../../../../step-forms/selectors') + +const render = () => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] +} + +describe('AddStepButton', () => { + beforeEach(() => { + vi.mocked(getEnableComment).mockReturnValue(true) + vi.mocked(getCurrentFormIsPresaved).mockReturnValue(false) + vi.mocked(getIsMultiSelectMode).mockReturnValue(false) + vi.mocked(getInitialDeckSetup).mockReturnValue({ + modules: { + hs: { + model: HEATERSHAKER_MODULE_V1, + type: HEATERSHAKER_MODULE_TYPE, + id: 'mockId', + moduleState: {} as any, + slot: 'A1', + }, + tc: { + model: THERMOCYCLER_MODULE_V1, + type: THERMOCYCLER_MODULE_TYPE, + id: 'mockId', + moduleState: {} as any, + slot: 'B1', + }, + temp: { + model: TEMPERATURE_MODULE_V1, + type: TEMPERATURE_MODULE_TYPE, + id: 'mockId', + moduleState: {} as any, + slot: 'C1', + }, + mag: { + model: MAGNETIC_MODULE_V1, + type: MAGNETIC_MODULE_TYPE, + id: 'mockId', + moduleState: {} as any, + slot: 'D1', + }, + }, + labware: {}, + additionalEquipmentOnDeck: {}, + pipettes: {}, + }) + }) + + it('renders add step button and clicking on it renders the overflow menu with all modules', () => { + render() + fireEvent.click(screen.getByText('+ Add Step')) + screen.getByText('Comment') + screen.getByText('Transfer') + screen.getByText('Mix') + screen.getByText('Pause') + screen.getByText('Thermocycler') + screen.getByText('Heater-shaker') + screen.getByText('Temperature') + screen.getByText('Magnet') + }) +}) diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/__tests__/StepContainer.test.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/__tests__/StepContainer.test.tsx new file mode 100644 index 00000000000..095a7a98684 --- /dev/null +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/__tests__/StepContainer.test.tsx @@ -0,0 +1,48 @@ +import * as React from 'react' +import { describe, it, vi, beforeEach, expect } from 'vitest' +import '@testing-library/jest-dom/vitest' +import { fireEvent, screen } from '@testing-library/react' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { i18n } from '../../../../../assets/localization' +import { getUnsavedForm } from '../../../../../step-forms/selectors' +import { StepContainer } from '../StepContainer' +import { StepOverflowMenu } from '../StepOverflowMenu' + +vi.mock('../../../../../step-forms/selectors') +vi.mock('../StepOverflowMenu') + +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] +} + +describe('StepContainer', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + title: 'Starting deck state', + iconName: 'add', + onClick: vi.fn(), + selected: false, + hovered: false, + stepId: 'mockStepId', + } + vi.mocked(StepOverflowMenu).mockReturnValue( +
mock StepOverflowMenu
+ ) + vi.mocked(getUnsavedForm).mockReturnValue(null) + }) + + it('renders the starting deck state step', () => { + render(props) + fireEvent.click(screen.getByText('Starting deck state')) + expect(props.onClick).toHaveBeenCalled() + }) + it('renders the ending deck state step', () => { + props.title = 'Final deck state' + render(props) + screen.getByText('Final deck state') + }) +}) diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/__tests__/StepOverflowMenu.test.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/__tests__/StepOverflowMenu.test.tsx new file mode 100644 index 00000000000..7188485493e --- /dev/null +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/__tests__/StepOverflowMenu.test.tsx @@ -0,0 +1,40 @@ +import * as React from 'react' +import { describe, it, vi, beforeEach, expect } from 'vitest' +import '@testing-library/jest-dom/vitest' +import { fireEvent, screen } from '@testing-library/react' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { i18n } from '../../../../../assets/localization' +import { StepOverflowMenu } from '../StepOverflowMenu' +import { deleteStep } from '../../../../../steplist/actions' +import { duplicateStep } from '../../../../../ui/steps/actions/thunks' + +vi.mock('../../../../../ui/steps/actions/thunks') +vi.mock('../../../../../steplist/actions') +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] +} + +describe('StepOverflowMenu', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + stepId: 'mockId', + top: 0, + menuRootRef: { current: null }, + } + }) + + it('renders each button and clicking them calls the action', () => { + render(props) + fireEvent.click(screen.getByText('Delete step')) + expect(vi.mocked(deleteStep)).toHaveBeenCalled() + fireEvent.click(screen.getByText('Duplicate step')) + expect(vi.mocked(duplicateStep)).toHaveBeenCalled() + fireEvent.click(screen.getByText('View commands')) + fireEvent.click(screen.getByText('Rename step')) + // TODO: wire up view commands and rename + }) +}) diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/__tests__/TimelineToolbox.test.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/__tests__/TimelineToolbox.test.tsx new file mode 100644 index 00000000000..3fa3204a1e6 --- /dev/null +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/__tests__/TimelineToolbox.test.tsx @@ -0,0 +1,46 @@ +import * as React from 'react' +import { describe, it, vi, beforeEach, expect } from 'vitest' +import { screen } from '@testing-library/react' +import { renderWithProviders } from '../../../../../__testing-utils__' +import { i18n } from '../../../../../assets/localization' +import { + getOrderedStepIds, + getUnsavedForm, +} from '../../../../../step-forms/selectors' +import { TimelineToolbox } from '../TimelineToolbox' +import { TerminalItemStep } from '../TerminalItemStep' +import { DraggableSteps } from '../DraggableSteps' +import { PresavedStep } from '../PresavedStep' +import { AddStepButton } from '../AddStepButton' + +vi.mock('../AddStepButton') +vi.mock('../DraggableSteps') +vi.mock('../PresavedStep') +vi.mock('../TerminalItemStep') +vi.mock('../../../../../step-forms/selectors') +const render = () => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] +} + +describe('TimelineToolbox', () => { + beforeEach(() => { + vi.mocked(getOrderedStepIds).mockReturnValue(['mock1Step']) + vi.mocked(getUnsavedForm).mockReturnValue(null) + vi.mocked(TerminalItemStep).mockReturnValue( +
mock TerminalItemStep
+ ) + vi.mocked(DraggableSteps).mockReturnValue(
mock DraggableSteps
) + vi.mocked(PresavedStep).mockReturnValue(
mock PresavedStep
) + vi.mocked(AddStepButton).mockReturnValue(
mock AddStepButton
) + }) + it('renders 2 terminal item steps, a draggable step and presaved step with toolbox title', () => { + render() + screen.getByText('Protocol timeline') + screen.getByText('mock AddStepButton') + screen.getByText('mock PresavedStep') + screen.getByText('mock DraggableSteps') + expect(screen.getAllByText('mock TerminalItemStep')).toHaveLength(2) + }) +}) diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/index.ts b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/index.ts new file mode 100644 index 00000000000..6c1a85d1efc --- /dev/null +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/index.ts @@ -0,0 +1 @@ +export * from './TimelineToolbox' diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/__tests__/ProtocolSteps.test.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/__tests__/ProtocolSteps.test.tsx new file mode 100644 index 00000000000..8f56f55e9be --- /dev/null +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/__tests__/ProtocolSteps.test.tsx @@ -0,0 +1,27 @@ +import * as React from 'react' +import { describe, it, vi } from 'vitest' +import { screen } from '@testing-library/react' +import { renderWithProviders } from '../../../../__testing-utils__' +import { DeckSetupContainer } from '../../DeckSetup' +import { TimelineToolbox } from '../Timeline' +import { ProtocolSteps } from '..' + +vi.mock('../../../../components/StepEditForm') +vi.mock('../../DeckSetup') +vi.mock('../Timeline') +const render = () => { + return renderWithProviders()[0] +} + +describe('ProtocolSteps', () => { + it('renders each component in ProtocolSteps', () => { + vi.mocked(TimelineToolbox).mockReturnValue(
mock TimelineToolbox
) + vi.mocked(DeckSetupContainer).mockReturnValue( +
mock DeckSetupContainer
+ ) + + render() + screen.getByText('mock TimelineToolbox') + screen.getByText('mock DeckSetupContainer') + }) +}) diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/index.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/index.tsx new file mode 100644 index 00000000000..e69875a5b93 --- /dev/null +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/index.tsx @@ -0,0 +1,20 @@ +import * as React from 'react' +import { COLORS, Flex, POSITION_ABSOLUTE, SPACING } from '@opentrons/components' +import { StepEditForm } from '../../../components/StepEditForm' +import { DeckSetupContainer } from '../DeckSetup' +import { TimelineToolbox } from './Timeline' + +export function ProtocolSteps(): JSX.Element { + return ( + <> + {/* TODO: wire up the step edit form designs */} + + + + + + + + + ) +} diff --git a/protocol-designer/src/pages/Designer/__tests__/Designer.test.tsx b/protocol-designer/src/pages/Designer/__tests__/Designer.test.tsx index 97ac029d447..575a96af55c 100644 --- a/protocol-designer/src/pages/Designer/__tests__/Designer.test.tsx +++ b/protocol-designer/src/pages/Designer/__tests__/Designer.test.tsx @@ -12,11 +12,13 @@ import { generateNewProtocol } from '../../../labware-ingred/actions' import { DeckSetupContainer } from '../DeckSetup' import { Designer } from '../index' import { LiquidsOverflowMenu } from '../LiquidsOverflowMenu' +import { ProtocolSteps } from '../ProtocolSteps' import type { NavigateFunction } from 'react-router-dom' const mockNavigate = vi.fn() +vi.mock('../ProtocolSteps') vi.mock('../../../labware-ingred/actions') vi.mock('../../../labware-ingred/selectors') vi.mock('../LiquidsOverflowMenu') @@ -44,6 +46,7 @@ const render = () => { describe('Designer', () => { beforeEach(() => { + vi.mocked(ProtocolSteps).mockReturnValue(
mock ProtocolSteps
) vi.mocked(getFileMetadata).mockReturnValue({ protocolName: 'mockProtocolName', created: 123, @@ -101,5 +104,9 @@ describe('Designer', () => { expect(vi.mocked(generateNewProtocol)).toHaveBeenCalled() }) - it.todo('renders the protocol steps page') + it('renders the protocol steps page', () => { + render() + fireEvent.click(screen.getByText('Protocol steps')) + screen.getByText('mock ProtocolSteps') + }) }) diff --git a/protocol-designer/src/pages/Designer/index.tsx b/protocol-designer/src/pages/Designer/index.tsx index 689405e8eba..e92b4b5e76e 100644 --- a/protocol-designer/src/pages/Designer/index.tsx +++ b/protocol-designer/src/pages/Designer/index.tsx @@ -19,6 +19,7 @@ import { ToggleGroup, useOnClickOutside, } from '@opentrons/components' +import { selectTerminalItem } from '../../ui/steps/actions/actions' import { useKitchen } from '../../organisms/Kitchen/hooks' import { getDeckSetupForActiveItem } from '../../top-selectors/labware-locations' import { generateNewProtocol } from '../../labware-ingred/actions' @@ -29,6 +30,7 @@ import { DeckSetupContainer } from './DeckSetup' import { selectors } from '../../labware-ingred/selectors' import { OffDeck } from './Offdeck' import { LiquidsOverflowMenu } from './LiquidsOverflowMenu' +import { ProtocolSteps } from './ProtocolSteps' import type { CutoutId } from '@opentrons/shared-data' import type { DeckSlot } from '@opentrons/step-generation' @@ -126,7 +128,18 @@ export function Designer(): JSX.Element { }) const deckViewItems = - deckView === leftString ? : + deckView === leftString ? ( + + ) : ( + + ) + + React.useEffect(() => { + if (tab === 'startingDeck') { + // ensure that the starting deck page is always showing the initial deck setup + dispatch(selectTerminalItem('__initial_setup__')) + } + }, [tab]) return ( <> @@ -185,17 +198,17 @@ export function Designer(): JSX.Element {
- - {tab === 'startingDeck' ? ( + {tab === 'startingDeck' ? ( + {zoomIn.slot == null ? ( @@ -214,10 +227,10 @@ export function Designer(): JSX.Element { ) : null} {deckViewItems} - ) : ( -
TODO wire this up
- )} -
+
+ ) : ( + + )}
) diff --git a/protocol-designer/src/pages/Designer/types.ts b/protocol-designer/src/pages/Designer/types.ts new file mode 100644 index 00000000000..6c9860a527f --- /dev/null +++ b/protocol-designer/src/pages/Designer/types.ts @@ -0,0 +1,3 @@ +export interface DeckSetupTabType { + tab: 'startingDeck' | 'protocolSteps' +} diff --git a/protocol-designer/src/pages/ProtocolOverview/UnusedModalContent.tsx b/protocol-designer/src/pages/ProtocolOverview/UnusedModalContent.tsx new file mode 100644 index 00000000000..f59f29044f5 --- /dev/null +++ b/protocol-designer/src/pages/ProtocolOverview/UnusedModalContent.tsx @@ -0,0 +1,198 @@ +import * as React from 'react' +import type { ModuleOnDeck, PipetteOnDeck } from '../../step-forms' +import type { Fixture } from './index' + +interface MissingContent { + noCommands: boolean + pipettesWithoutStep: PipetteOnDeck[] + modulesWithoutStep: ModuleOnDeck[] + gripperWithoutStep: boolean + fixtureWithoutStep: Fixture + t: any +} + +interface WarningContent { + content: React.ReactNode + heading: string +} + +// TODO(ja): update this to use StyledText +export function getWarningContent({ + noCommands, + pipettesWithoutStep, + modulesWithoutStep, + gripperWithoutStep, + fixtureWithoutStep, + t, +}: MissingContent): WarningContent | null { + if (noCommands) { + return { + content: ( + <> +

{t('alert:export_warnings.no_commands.body1')}

+

{t('alert:export_warnings.no_commands.body2')}

+ + ), + heading: t('alert:export_warnings.no_commands.heading'), + } + } + + if (gripperWithoutStep) { + return { + content: ( + <> +

{t('alert:export_warnings.unused_gripper.body1')}

+

{t('alert:export_warnings.unused_gripper.body2')}

+ + ), + heading: t('alert:export_warnings.unused_gripper.heading'), + } + } + + const pipettesDetails = pipettesWithoutStep + .map(pipette => + pipette.spec.channels === 96 + ? `${pipette.spec.displayName} pipette` + : `${pipette.mount} ${pipette.spec.displayName} pipette` + ) + .join(' and ') + + const unusedModuleCounts = modulesWithoutStep.reduce<{ + [key: string]: number + }>((acc, mod) => { + if (!(mod.type in acc)) { + return { ...acc, [mod.type]: 1 } + } else { + return { ...acc, [mod.type]: acc[mod.type] + 1 } + } + }, {}) + + const modulesDetails = Object.keys(unusedModuleCounts) + // sorting by module count + .sort((k1, k2) => { + if (unusedModuleCounts[k1] < unusedModuleCounts[k2]) { + return 1 + } else if (unusedModuleCounts[k1] > unusedModuleCounts[k2]) { + return -1 + } else { + return 0 + } + }) + .map(modType => + unusedModuleCounts[modType] === 1 + ? t(`modules:module_long_names.${modType}`) + : `${t(`modules:module_long_names.${modType}`)}s` + ) + // transform list of modules with counts to string + .reduce((acc, modName, index, arr) => { + if (arr.length > 2) { + if (index === arr.length - 1) { + return `${acc} and ${modName}` + } else { + return `${acc}${modName}, ` + } + } else if (arr.length === 2) { + return index === 0 ? `${modName} and ` : `${acc}${modName}` + } else { + return modName + } + }, '') + + if (pipettesWithoutStep.length && modulesWithoutStep.length) { + return { + content: ( + <> +

+ {t('alert:export_warnings.unused_pipette_and_module.body1', { + modulesDetails, + pipettesDetails, + })} +

+

{t('alert:export_warnings.unused_pipette_and_module.body2')}

+ + ), + heading: t('alert:export_warnings.unused_pipette_and_module.heading'), + } + } + + if (pipettesWithoutStep.length) { + return { + content: ( + <> +

+ {t('alert:export_warnings.unused_pipette.body1', { + pipettesDetails, + })} +

+

{t('alert:export_warnings.unused_pipette.body2')}

+ + ), + heading: t('alert:export_warnings.unused_pipette.heading'), + } + } + + if (modulesWithoutStep.length) { + const moduleCase = + modulesWithoutStep.length > 1 ? 'unused_modules' : 'unused_module' + const slotName = modulesWithoutStep.map(module => module.slot) + return { + content: ( + <> +

+ {t(`alert:export_warnings.${moduleCase}.body1`, { + modulesDetails, + slotName: slotName, + })} +

+

{t(`alert:export_warnings.${moduleCase}.body2`)}

+ + ), + heading: t(`alert:export_warnings.${moduleCase}.heading`), + } + } + + if (fixtureWithoutStep.trashBin || fixtureWithoutStep.wasteChute) { + return { + content: + (fixtureWithoutStep.trashBin && !fixtureWithoutStep.wasteChute) || + (!fixtureWithoutStep.trashBin && fixtureWithoutStep.wasteChute) ? ( +

+ {t('alert:export_warnings.unused_trash.body', { + name: fixtureWithoutStep.trashBin ? 'trash bin' : 'waste chute', + })} +

+ ) : ( +

+ {t('alert:export_warnings.unused_trash.body_both', { + trashName: 'trash bin', + wasteName: 'waste chute', + })} +

+ ), + heading: t('alert:export_warnings.unused_trash.heading'), + } + } + + if (fixtureWithoutStep.stagingAreaSlots.length) { + return { + content: ( + <> +

+ {t('alert:export_warnings.unused_staging_area.body1', { + count: fixtureWithoutStep.stagingAreaSlots.length, + slot: fixtureWithoutStep.stagingAreaSlots, + })} +

+

+ {t('alert:export_warnings.unused_staging_area.body2', { + count: fixtureWithoutStep.stagingAreaSlots.length, + })} +

+ + ), + heading: t('alert:export_warnings.unused_staging_area.heading'), + } + } + + return null +} diff --git a/protocol-designer/src/pages/ProtocolOverview/__tests__/ProtocolOverview.test.tsx b/protocol-designer/src/pages/ProtocolOverview/__tests__/ProtocolOverview.test.tsx index 81840da58f0..edbde2d96d2 100644 --- a/protocol-designer/src/pages/ProtocolOverview/__tests__/ProtocolOverview.test.tsx +++ b/protocol-designer/src/pages/ProtocolOverview/__tests__/ProtocolOverview.test.tsx @@ -12,7 +12,6 @@ import { getInitialDeckSetup, getSavedStepForms, } from '../../../step-forms/selectors' -import { useBlockingHint } from '../../../components/Hints/useBlockingHint' import { MaterialsListModal } from '../../../organisms/MaterialsListModal' import { selectors as labwareIngredSelectors } from '../../../labware-ingred/selectors' import { ProtocolOverview } from '../index' @@ -25,7 +24,6 @@ vi.mock('../OffdeckThumbnail') vi.mock('../DeckThumbnail') vi.mock('../../../step-forms/selectors') vi.mock('../../../file-data/selectors') -vi.mock('../../../components/Hints/useBlockingHint') vi.mock('../../../organisms/MaterialsListModal') vi.mock('../../../labware-ingred/selectors') vi.mock('../../../organisms') @@ -68,7 +66,6 @@ describe('ProtocolOverview', () => { description: 'mockDescription', created: 123, }) - vi.mocked(useBlockingHint).mockReturnValue(null) vi.mocked(MaterialsListModal).mockReturnValue(
mock MaterialsListModal
) @@ -134,14 +131,6 @@ describe('ProtocolOverview', () => { expect(mockNavigate).toHaveBeenCalledWith('/designer') }) - it('renders the file sidebar and exports with blocking hint for exporting', () => { - vi.mocked(useBlockingHint).mockReturnValue(
mock blocking hint
) - render() - fireEvent.click(screen.getByRole('button', { name: 'Export protocol' })) - expect(vi.mocked(useBlockingHint)).toHaveBeenCalled() - screen.getByText('mock blocking hint') - }) - it('render mock materials list modal when clicking materials list', () => { render() fireEvent.click(screen.getByText('Materials list')) diff --git a/protocol-designer/src/pages/ProtocolOverview/index.tsx b/protocol-designer/src/pages/ProtocolOverview/index.tsx index 3512db95f3a..6acf8d2e770 100644 --- a/protocol-designer/src/pages/ProtocolOverview/index.tsx +++ b/protocol-designer/src/pages/ProtocolOverview/index.tsx @@ -4,6 +4,7 @@ import { useNavigate } from 'react-router-dom' import { useDispatch, useSelector } from 'react-redux' import { format } from 'date-fns' import { css } from 'styled-components' +import { createPortal } from 'react-dom' import { ALIGN_CENTER, @@ -11,13 +12,17 @@ import { DIRECTION_COLUMN, Flex, InfoScreen, + JUSTIFY_END, JUSTIFY_FLEX_END, JUSTIFY_SPACE_BETWEEN, LargeButton, LiquidIcon, ListItem, ListItemDescriptor, + Modal, + PrimaryButton, SPACING, + SecondaryButton, StyledText, TYPOGRAPHY, ToggleGroup, @@ -40,11 +45,9 @@ import { getUnusedStagingAreas, getUnusedTrash, } from '../../components/FileSidebar/utils' -import { resetScrollElements } from '../../ui/steps/utils' -import { useBlockingHint } from '../../components/Hints/useBlockingHint' -import { v8WarningContent } from '../../components/FileSidebar/FileSidebar' import { MaterialsListModal } from '../../organisms/MaterialsListModal' import { BUTTON_LINK_STYLE } from '../../atoms' +import { getMainPagePortalEl } from '../../components/portals/MainPageModalPortal' import { EditProtocolMetadataModal, EditInstrumentsModal, @@ -52,11 +55,11 @@ import { } from '../../organisms' import { DeckThumbnail } from './DeckThumbnail' import { OffDeckThumbnail } from './OffdeckThumbnail' +import { getWarningContent } from './UnusedModalContent' import type { CreateCommand, PipetteName } from '@opentrons/shared-data' import type { DeckSlot } from '@opentrons/step-generation' import type { ThunkDispatch } from '../../types' -import type { HintKey } from '../../tutorial' const REQUIRED_APP_VERSION = '8.0.0' const DATE_ONLY_FORMAT = 'MMMM dd, yyyy' @@ -69,7 +72,7 @@ const LOAD_COMMANDS: Array = [ 'loadLiquid', ] -interface Fixture { +export interface Fixture { trashBin: boolean wasteChute: boolean stagingAreaSlots: string[] @@ -91,6 +94,10 @@ export function ProtocolOverview(): JSX.Element { showEditMetadataModal, setShowEditMetadataModal, ] = React.useState(false) + const [ + showExportWarningModal, + setShowExportWarningModal, + ] = React.useState(false) const formValues = useSelector(fileSelectors.getFileMetadata) const robotType = useSelector(fileSelectors.getRobotType) const initialDeckSetup = useSelector(getInitialDeckSetup) @@ -99,7 +106,6 @@ export function ProtocolOverview(): JSX.Element { ) const dispatch: ThunkDispatch = useDispatch() const [hover, setHover] = React.useState(null) - const [showBlockingHint, setShowBlockingHint] = React.useState(false) const [ showMaterialsListModal, setShowMaterialsListModal, @@ -204,31 +210,20 @@ export function ProtocolOverview(): JSX.Element { fixtureWithoutStep.wasteChute || fixtureWithoutStep.stagingAreaSlots.length > 0 - const getExportHintContent = (): { - hintKey: HintKey - content: React.ReactNode - } => { - return { - hintKey: t('alert:export_v8_1_protocol_7_3'), - content: v8WarningContent(t), - } - } - - const { hintKey, content } = getExportHintContent() - - const blockingExportHint = useBlockingHint({ - enabled: showBlockingHint, - hintKey, - content, - handleCancel: () => { - setShowBlockingHint(false) - }, - handleContinue: () => { - setShowBlockingHint(false) - dispatch(loadFileActions.saveProtocolFile()) - }, - }) + const warning = + hasWarning && + getWarningContent({ + noCommands, + pipettesWithoutStep, + modulesWithoutStep, + gripperWithoutStep, + fixtureWithoutStep, + t, + }) + const cancelModal = (): void => { + setShowExportWarningModal(false) + } return ( <> {showEditMetadataModal ? ( @@ -245,7 +240,35 @@ export function ProtocolOverview(): JSX.Element { }} /> ) : null} - {blockingExportHint} + {showExportWarningModal && + createPortal( + + + {t('shared:cancel')} + + { + setShowExportWarningModal(false) + dispatch(loadFileActions.saveProtocolFile()) + }} + > + {t('alert:continue_with_export')} + +
+ } + > + {warning && warning.content} + , + getMainPagePortalEl() + )} {showMaterialsListModal ? ( { - // ToDo (kk:08/26/2024) should use hasWarning later - if (!hasWarning) { - resetScrollElements() - // ToDo (kk:08/26/2024) create warning modal + if (hasWarning) { + setShowExportWarningModal(true) } else { - resetScrollElements() - setShowBlockingHint(true) + dispatch(loadFileActions.saveProtocolFile()) } }} iconName="arrow-right" From 4427fdd4fd2b59623128d3fdfaedb68b57f7bc55 Mon Sep 17 00:00:00 2001 From: syao1226 <146495172+syao1226@users.noreply.github.com> Date: Wed, 18 Sep 2024 11:12:41 -0400 Subject: [PATCH 23/24] fix(protocol-designer): add hover state to buttons (#16277) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes RQA-3190 # Overview Adding a hover state to the Swap Pipettes and Remove buttons on the `EditInstrumentsModal` so that when the mouse hovers over the buttons, the color changes to light grey. ## Test Plan and Hands on Testing ![Screenshot 2024-09-17 at 4 18 08 PM](https://github.com/user-attachments/assets/f65af8df-4c48-47e2-8e94-93c95dc1028a) ## Changelog - Added the `BUTTON_LINK_STYLE` css const to Swap pipettes and Remove buttons. ## Review requests ## Risk assessment --------- Co-authored-by: shiyaochen --- .../organisms/EditInstrumentsModal/index.tsx | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/protocol-designer/src/organisms/EditInstrumentsModal/index.tsx b/protocol-designer/src/organisms/EditInstrumentsModal/index.tsx index fbaa2fb8954..b77811de040 100644 --- a/protocol-designer/src/organisms/EditInstrumentsModal/index.tsx +++ b/protocol-designer/src/organisms/EditInstrumentsModal/index.tsx @@ -60,6 +60,7 @@ import { setFeatureFlags } from '../../feature-flags/actions' import { createCustomTiprackDef } from '../../labware-defs/actions' import { deleteContainer } from '../../labware-ingred/actions' import { selectors as stepFormSelectors } from '../../step-forms' +import { BUTTON_LINK_STYLE } from '../../atoms' import { getSectionsFromPipetteName } from './utils' import { editPipettes } from './editPipettes' import type { PipetteMount, PipetteName } from '@opentrons/shared-data' @@ -209,6 +210,7 @@ export function EditInstrumentsModal( {has96Channel ? null : ( dispatch( changeSavedStepForm({ @@ -220,7 +222,7 @@ export function EditInstrumentsModal( ) } > - + - - { - dispatch(toggleIsGripperRequired()) - }} - > - - {t('remove')} - - - + { + dispatch(toggleIsGripperRequired()) + }} + > + + {t('remove')} + + ) : ( From 869e954885fa7004ce58812e6ef3834dfdbefcf7 Mon Sep 17 00:00:00 2001 From: koji Date: Wed, 18 Sep 2024 11:49:42 -0400 Subject: [PATCH 24/24] feat(components): add css props and style props to components (#16136) * feat(components): add css props and style props to components --- .../DesignTokens/Colors/Colors.stories.tsx | 5 ++-- .../atoms/buttons/FloatingActionButton.tsx | 3 +- app/src/atoms/buttons/IconButton.tsx | 3 +- app/src/atoms/buttons/MediumButton.tsx | 5 ++-- app/src/atoms/buttons/SmallButton.tsx | 3 +- .../molecules/CollapsibleSection/index.tsx | 3 +- .../molecules/InstrumentCard/MenuOverlay.tsx | 3 +- app/src/molecules/InterventionModal/index.tsx | 10 ++++--- .../JogControls/TouchControlButton.tsx | 4 +-- .../MiniCard/__tests__/MiniCard.test.tsx | 8 ++--- app/src/molecules/MiniCard/index.tsx | 10 +++++-- app/src/molecules/UploadInput/index.tsx | 5 ++-- app/src/organisms/ChildNavigation/index.tsx | 3 +- .../ChooseProtocolSlideout/index.tsx | 11 ++++--- .../organisms/ChooseRobotSlideout/index.tsx | 9 ++++-- .../index.tsx | 2 +- .../AddFixtureModal.tsx | 3 +- .../Devices/HistoricalProtocolRun.tsx | 3 +- .../HistoricalProtocolRunOverflowMenu.tsx | 2 ++ .../Devices/PipetteCard/FlexPipetteCard.tsx | 9 ++++-- .../PipetteCard/PipetteOverflowMenu.tsx | 3 +- .../Devices/ProtocolRun/ProtocolRunSetup.tsx | 13 +++++---- .../SetupLiquids/LiquidDetailCard.tsx | 5 ++-- .../SetupLiquids/SetupLiquidsList.tsx | 7 +++-- .../organisms/Devices/RobotOverflowMenu.tsx | 3 +- .../Devices/RobotOverviewOverflowMenu.tsx | 3 +- .../DropTipWizardFlows/BeforeBeginning.tsx | 5 ++-- .../ErrorRecoveryWizard.tsx | 4 +-- app/src/organisms/GripperCard/index.tsx | 9 ++++-- .../LabwareCard/CustomLabwareOverflowMenu.tsx | 3 +- .../ModuleCard/ModuleOverflowMenu.tsx | 3 +- app/src/organisms/ModuleCard/hooks.tsx | 11 +++++-- app/src/organisms/Navigation/index.tsx | 29 ++++++++++--------- .../RobotSettingButton.tsx | 5 ++-- .../RobotSettingsDashboard/UpdateChannel.tsx | 3 +- .../PipetteWizardFlows/ChoosePipette.tsx | 5 ++-- .../ProtocolLabwareDetails.tsx | 3 +- .../ProtocolsLanding/ProtocolList.tsx | 3 +- .../ProtocolsLanding/ProtocolOverflowMenu.tsx | 3 +- .../CalibrationDetails/OverflowMenu.tsx | 3 +- .../RobotSettingsGripperCalibration.tsx | 3 +- app/src/organisms/RunProgressMeter/index.tsx | 4 ++- app/src/pages/Desktop/Labware/index.tsx | 7 +++-- app/src/pages/ODD/InstrumentDetail/index.tsx | 13 +++++---- .../ODD/ProtocolDashboard/ProtocolCard.tsx | 3 +- .../QuickTransferCard.tsx | 7 +++-- components/src/atoms/Checkbox/index.tsx | 6 ++-- components/src/atoms/CheckboxField/index.tsx | 6 ++-- .../ListButtonRadioButton.tsx | 3 +- components/src/atoms/ListButton/index.tsx | 3 +- components/src/atoms/Toast/index.tsx | 5 ++-- components/src/atoms/buttons/LargeButton.tsx | 6 ++-- components/src/atoms/buttons/RadioButton.tsx | 9 ++++-- .../src/atoms/buttons/SecondaryButton.tsx | 6 ++-- components/src/modals/ModalShell.tsx | 3 +- .../src/molecules/DropdownMenu/index.tsx | 3 +- components/src/primitives/Btn.tsx | 6 ++-- components/src/primitives/Link.tsx | 3 +- components/src/primitives/style-props.ts | 1 + components/src/styles/cursor.ts | 12 ++++++++ components/src/styles/flexbox.ts | 6 ++++ components/src/styles/index.ts | 1 + components/src/tooltips/LegacyTooltip.tsx | 3 +- .../organisms/MainContentContainer/index.tsx | 6 ++-- protocol-designer/src/NavigationBar.tsx | 3 +- .../src/components/ComputingSpinner.tsx | 4 +-- .../CreateFileWizard/EquipmentOption.tsx | 23 ++++++++------- .../modals/CreateFileWizard/RobotTypeTile.tsx | 21 +++++++------- .../organisms/EditInstrumentsModal/index.tsx | 3 +- .../SelectPipettes.tsx | 3 +- .../Designer/DeckSetup/DeckItemHover.tsx | 3 +- .../pages/Designer/DeckSetup/LabwareTools.tsx | 3 +- .../Designer/DeckSetup/SlotOverflowMenu.tsx | 6 ++-- .../pages/Designer/LiquidsOverflowMenu.tsx | 6 ++-- protocol-designer/src/pages/Landing/index.tsx | 3 +- .../src/pages/ProtocolOverview/index.tsx | 9 +++--- 76 files changed, 275 insertions(+), 164 deletions(-) create mode 100644 components/src/styles/cursor.ts diff --git a/app/src/DesignTokens/Colors/Colors.stories.tsx b/app/src/DesignTokens/Colors/Colors.stories.tsx index 3a28cda86c3..60bbdff3945 100644 --- a/app/src/DesignTokens/Colors/Colors.stories.tsx +++ b/app/src/DesignTokens/Colors/Colors.stories.tsx @@ -3,12 +3,13 @@ import { ALIGN_FLEX_START, BORDERS, COLORS, + CURSOR_POINTER, DIRECTION_COLUMN, DIRECTION_ROW, Flex, JUSTIFY_SPACE_BETWEEN, - SPACING, LegacyStyledText, + SPACING, TYPOGRAPHY, } from '@opentrons/components' @@ -74,7 +75,7 @@ const Template: Story = args => { margin={SPACING.spacing2} // Add some margin between color rows borderRadius={BORDERS.borderRadius4} style={{ - cursor: 'pointer', + cursor: CURSOR_POINTER, border: `1px solid ${COLORS.grey20}`, }} > diff --git a/app/src/atoms/buttons/FloatingActionButton.tsx b/app/src/atoms/buttons/FloatingActionButton.tsx index 5905bdd8fce..76a72cd076b 100644 --- a/app/src/atoms/buttons/FloatingActionButton.tsx +++ b/app/src/atoms/buttons/FloatingActionButton.tsx @@ -6,6 +6,7 @@ import { BORDERS, Btn, COLORS, + CURSOR_DEFAULT, DIRECTION_ROW, Flex, Icon, @@ -33,7 +34,7 @@ export function FloatingActionButton( border-radius: ${BORDERS.borderRadius40}; box-shadow: ${BORDERS.shadowBig}; color: ${contentColor}; - cursor: default; + cursor: ${CURSOR_DEFAULT}; &:active { background-color: ${COLORS.purple55}; diff --git a/app/src/atoms/buttons/IconButton.tsx b/app/src/atoms/buttons/IconButton.tsx index 6506b267529..d86324ed843 100644 --- a/app/src/atoms/buttons/IconButton.tsx +++ b/app/src/atoms/buttons/IconButton.tsx @@ -4,6 +4,7 @@ import { BORDERS, Btn, COLORS, + CURSOR_DEFAULT, Icon, RESPONSIVENESS, } from '@opentrons/components' @@ -39,7 +40,7 @@ export function IconButton(props: IconButtonProps): JSX.Element { color: ${COLORS.grey50}; } @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - cursor: default; + cursor: ${CURSOR_DEFAULT}; } `} {...buttonProps} diff --git a/app/src/atoms/buttons/MediumButton.tsx b/app/src/atoms/buttons/MediumButton.tsx index 7634f3e428f..b25ac0432b9 100644 --- a/app/src/atoms/buttons/MediumButton.tsx +++ b/app/src/atoms/buttons/MediumButton.tsx @@ -5,12 +5,13 @@ import { BORDERS, Btn, COLORS, + CURSOR_DEFAULT, DIRECTION_ROW, DISPLAY_FLEX, Icon, JUSTIFY_CENTER, - SPACING, LegacyStyledText, + SPACING, TYPOGRAPHY, } from '@opentrons/components' import { ODD_FOCUS_VISIBLE } from './constants' @@ -111,7 +112,7 @@ export function MediumButton(props: MediumButtonProps): JSX.Element { : BORDERS.borderRadius16}; box-shadow: none; color: ${MEDIUM_BUTTON_PROPS_BY_TYPE[buttonType].defaultColor}; - cursor: default; + cursor: ${CURSOR_DEFAULT}; &:focus { background-color: ${MEDIUM_BUTTON_PROPS_BY_TYPE[buttonType] diff --git a/app/src/atoms/buttons/SmallButton.tsx b/app/src/atoms/buttons/SmallButton.tsx index 25a494a1488..a177be5a3e0 100644 --- a/app/src/atoms/buttons/SmallButton.tsx +++ b/app/src/atoms/buttons/SmallButton.tsx @@ -5,6 +5,7 @@ import { BORDERS, Btn, COLORS, + CURSOR_DEFAULT, DIRECTION_ROW, Flex, Icon, @@ -105,7 +106,7 @@ export function SmallButton(props: SmallButtonProps): JSX.Element { color: ${SMALL_BUTTON_PROPS_BY_TYPE[buttonType].defaultColor}; background-color: ${SMALL_BUTTON_PROPS_BY_TYPE[buttonType] .defaultBackgroundColor}; - cursor: default; + cursor: ${CURSOR_DEFAULT}; border-radius: ${buttonCategory === 'rounded' ? BORDERS.borderRadius40 : BORDERS.borderRadius16}; diff --git a/app/src/molecules/CollapsibleSection/index.tsx b/app/src/molecules/CollapsibleSection/index.tsx index a11e73be358..3b9d6c4b8d0 100644 --- a/app/src/molecules/CollapsibleSection/index.tsx +++ b/app/src/molecules/CollapsibleSection/index.tsx @@ -4,6 +4,7 @@ import { css } from 'styled-components' import { Btn, COLORS, + CURSOR_POINTER, DIRECTION_COLUMN, Flex, Icon, @@ -42,7 +43,7 @@ export function CollapsibleSection( setIsExpanded(!isExpanded) }} css={{ - cursor: 'pointer', + cursor: CURSOR_POINTER, }} > { e.preventDefault() diff --git a/app/src/molecules/InterventionModal/index.tsx b/app/src/molecules/InterventionModal/index.tsx index aec8c9fea22..f3f784d22f1 100644 --- a/app/src/molecules/InterventionModal/index.tsx +++ b/app/src/molecules/InterventionModal/index.tsx @@ -6,6 +6,9 @@ import { ALIGN_CENTER, BORDERS, COLORS, + CURSOR_DEFAULT, + CURSOR_POINTER, + DIRECTION_COLUMN, Flex, Icon, JUSTIFY_CENTER, @@ -14,9 +17,8 @@ import { POSITION_ABSOLUTE, POSITION_RELATIVE, POSITION_STICKY, - SPACING, - DIRECTION_COLUMN, RESPONSIVENESS, + SPACING, } from '@opentrons/components' import { getIsOnDevice } from '../../redux/config' @@ -101,7 +103,7 @@ const WRAPPER_STYLE = { bottom: '0', zIndex: '1', backgroundColor: `${COLORS.black90}${COLORS.opacity40HexCode}`, - cursor: 'default', + cursor: CURSOR_DEFAULT, 'data-testid': '__otInterventionModalWrapper', } as const @@ -179,7 +181,7 @@ const ICON_STYLE = css` width: ${SPACING.spacing16}; height: ${SPACING.spacing16}; margin: ${SPACING.spacing4}; - cursor: pointer; + cursor: ${CURSOR_POINTER}; @media (${RESPONSIVENESS.touchscreenMediaQuerySpecs}) { width: ${SPACING.spacing32}; diff --git a/app/src/molecules/JogControls/TouchControlButton.tsx b/app/src/molecules/JogControls/TouchControlButton.tsx index 0ad85f1de7d..859541b50de 100644 --- a/app/src/molecules/JogControls/TouchControlButton.tsx +++ b/app/src/molecules/JogControls/TouchControlButton.tsx @@ -1,12 +1,12 @@ import styled from 'styled-components' -import { COLORS, SPACING, BORDERS } from '@opentrons/components' +import { BORDERS, COLORS, CURSOR_DEFAULT, SPACING } from '@opentrons/components' import { ODD_FOCUS_VISIBLE } from '../../atoms/buttons/constants' export const TouchControlButton = styled.button<{ selected: boolean }>` background-color: ${({ selected }) => selected ? COLORS.blue50 : COLORS.blue35}; - cursor: default; + cursor: ${CURSOR_DEFAULT}; border-radius: ${BORDERS.borderRadius16}; box-shadow: none; padding: ${SPACING.spacing8} ${SPACING.spacing20}; diff --git a/app/src/molecules/MiniCard/__tests__/MiniCard.test.tsx b/app/src/molecules/MiniCard/__tests__/MiniCard.test.tsx index 6a7c3efd034..774bb76f20c 100644 --- a/app/src/molecules/MiniCard/__tests__/MiniCard.test.tsx +++ b/app/src/molecules/MiniCard/__tests__/MiniCard.test.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' import { fireEvent, screen } from '@testing-library/react' -import { COLORS, SPACING, BORDERS } from '@opentrons/components' +import { COLORS, SPACING, BORDERS, CURSOR_POINTER } from '@opentrons/components' import { renderWithProviders } from '../../../__testing-utils__' import { MiniCard } from '../' @@ -30,7 +30,7 @@ describe('MiniCard', () => { expect(miniCard).toHaveStyle(`border-radius: ${BORDERS.borderRadius8}`) expect(miniCard).toHaveStyle(`padding: ${SPACING.spacing8}`) expect(miniCard).toHaveStyle(`width: 100%`) - expect(miniCard).toHaveStyle(`cursor: pointer`) + expect(miniCard).toHaveStyle(`cursor: ${CURSOR_POINTER}`) }) it('renders the correct style selectedOptionStyles', () => { @@ -42,7 +42,7 @@ describe('MiniCard', () => { expect(miniCard).toHaveStyle(`border-radius: ${BORDERS.borderRadius8}`) expect(miniCard).toHaveStyle(`padding: ${SPACING.spacing8}`) expect(miniCard).toHaveStyle(`width: 100%`) - expect(miniCard).toHaveStyle(`cursor: pointer`) + expect(miniCard).toHaveStyle(`cursor: ${CURSOR_POINTER}`) }) it('renders the correct style errorOptionStyles', () => { @@ -55,7 +55,7 @@ describe('MiniCard', () => { expect(miniCard).toHaveStyle(`border-radius: ${BORDERS.borderRadius8}`) expect(miniCard).toHaveStyle(`padding: ${SPACING.spacing8}`) expect(miniCard).toHaveStyle(`width: 100%`) - expect(miniCard).toHaveStyle(`cursor: pointer`) + expect(miniCard).toHaveStyle(`cursor: ${CURSOR_POINTER}`) }) it('calls mock function when clicking mini card', () => { diff --git a/app/src/molecules/MiniCard/index.tsx b/app/src/molecules/MiniCard/index.tsx index 2ae0f6724ad..526b2076ef4 100644 --- a/app/src/molecules/MiniCard/index.tsx +++ b/app/src/molecules/MiniCard/index.tsx @@ -1,6 +1,12 @@ import * as React from 'react' import { css } from 'styled-components' -import { SPACING, Flex, COLORS, BORDERS } from '@opentrons/components' +import { + BORDERS, + COLORS, + CURSOR_POINTER, + Flex, + SPACING, +} from '@opentrons/components' import type { StyleProps } from '@opentrons/components' @@ -17,7 +23,7 @@ const unselectedOptionStyles = css` border-radius: ${BORDERS.borderRadius8}; padding: ${SPACING.spacing8}; width: 100%; - cursor: pointer; + cursor: ${CURSOR_POINTER}; &:hover { background-color: ${COLORS.grey10}; diff --git a/app/src/molecules/UploadInput/index.tsx b/app/src/molecules/UploadInput/index.tsx index 9802f6d28b5..77dc5a2616d 100644 --- a/app/src/molecules/UploadInput/index.tsx +++ b/app/src/molecules/UploadInput/index.tsx @@ -5,21 +5,22 @@ import { ALIGN_CENTER, BORDERS, COLORS, + CURSOR_POINTER, DIRECTION_COLUMN, DISPLAY_FLEX, Flex, Icon, JUSTIFY_CENTER, + LegacyStyledText, POSITION_FIXED, PrimaryButton, SPACING, - LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' const StyledLabel = styled.label` display: ${DISPLAY_FLEX}; - cursor: pointer; + cursor: ${CURSOR_POINTER}; flex-direction: ${DIRECTION_COLUMN}; align-items: ${ALIGN_CENTER}; width: 100%; diff --git a/app/src/organisms/ChildNavigation/index.tsx b/app/src/organisms/ChildNavigation/index.tsx index 00e8d02c36b..da215a69e1a 100644 --- a/app/src/organisms/ChildNavigation/index.tsx +++ b/app/src/organisms/ChildNavigation/index.tsx @@ -14,6 +14,7 @@ import { SPACING, LegacyStyledText, TYPOGRAPHY, + CURSOR_DEFAULT, } from '@opentrons/components' import { ODD_FOCUS_VISIBLE } from '../../atoms/buttons/constants' @@ -128,6 +129,6 @@ const IconButton = styled('button')` background-color: transparent; } @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - cursor: default; + cursor: ${CURSOR_DEFAULT}; } ` diff --git a/app/src/organisms/ChooseProtocolSlideout/index.tsx b/app/src/organisms/ChooseProtocolSlideout/index.tsx index a5c473982cf..119121fb90c 100644 --- a/app/src/organisms/ChooseProtocolSlideout/index.tsx +++ b/app/src/organisms/ChooseProtocolSlideout/index.tsx @@ -10,6 +10,9 @@ import { BORDERS, Box, COLORS, + CURSOR_AUTO, + CURSOR_DEFAULT, + CURSOR_POINTER, DIRECTION_COLUMN, DIRECTION_ROW, DISPLAY_BLOCK, @@ -549,7 +552,7 @@ export function ChooseProtocolSlideoutComponent( }} css={css` &:hover { - cursor: auto; + cursor: ${CURSOR_AUTO}; } `} > @@ -611,7 +614,7 @@ export function ChooseProtocolSlideoutComponent( @@ -926,13 +929,13 @@ function StoredProtocolList(props: StoredProtocolListProps): JSX.Element { const ENABLED_LINK_CSS = css` ${TYPOGRAPHY.linkPSemiBold} - cursor: pointer; + cursor: ${CURSOR_POINTER}; ` const DISABLED_LINK_CSS = css` ${TYPOGRAPHY.linkPSemiBold} color: ${COLORS.grey40}; - cursor: default; + cursor: ${CURSOR_DEFAULT}; &:hover { color: ${COLORS.grey40}; diff --git a/app/src/organisms/ChooseRobotSlideout/index.tsx b/app/src/organisms/ChooseRobotSlideout/index.tsx index 532be4f7813..629634840fb 100644 --- a/app/src/organisms/ChooseRobotSlideout/index.tsx +++ b/app/src/organisms/ChooseRobotSlideout/index.tsx @@ -9,6 +9,9 @@ import { ALIGN_FLEX_END, BORDERS, COLORS, + CURSOR_AUTO, + CURSOR_DEFAULT, + CURSOR_POINTER, DIRECTION_COLUMN, DIRECTION_ROW, DISPLAY_INLINE_BLOCK, @@ -644,7 +647,7 @@ export function ChooseRobotSlideout( }} css={css` &:hover { - cursor: auto; + cursor: ${CURSOR_AUTO}; } `} > @@ -682,13 +685,13 @@ export function ChooseRobotSlideout( const ENABLED_LINK_CSS = css` ${TYPOGRAPHY.linkPSemiBold} - cursor: pointer; + cursor: ${CURSOR_POINTER}; ` const DISABLED_LINK_CSS = css` ${TYPOGRAPHY.linkPSemiBold} color: ${COLORS.grey40}; - cursor: default; + cursor: ${CURSOR_DEFAULT}; &:hover { color: ${COLORS.grey40}; diff --git a/app/src/organisms/ChooseRobotToRunProtocolSlideout/index.tsx b/app/src/organisms/ChooseRobotToRunProtocolSlideout/index.tsx index 5e9a4fe7241..a715291e351 100644 --- a/app/src/organisms/ChooseRobotToRunProtocolSlideout/index.tsx +++ b/app/src/organisms/ChooseRobotToRunProtocolSlideout/index.tsx @@ -275,7 +275,7 @@ export function ChooseRobotToRunProtocolSlideoutComponent( diff --git a/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.tsx b/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.tsx index ff456be6d13..aff67233560 100644 --- a/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.tsx +++ b/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.tsx @@ -15,6 +15,7 @@ import { SPACING, Modal, TYPOGRAPHY, + CURSOR_DEFAULT, } from '@opentrons/components' import { useModulesQuery, @@ -385,7 +386,7 @@ export function AddFixtureModal({ const FIXTURE_BUTTON_STYLE_ODD = css` background-color: ${COLORS.grey35}; - cursor: default; + cursor: ${CURSOR_DEFAULT}; border-radius: ${BORDERS.borderRadius16}; box-shadow: none; diff --git a/app/src/organisms/Devices/HistoricalProtocolRun.tsx b/app/src/organisms/Devices/HistoricalProtocolRun.tsx index 5fc5b3e2580..0309c227edc 100644 --- a/app/src/organisms/Devices/HistoricalProtocolRun.tsx +++ b/app/src/organisms/Devices/HistoricalProtocolRun.tsx @@ -12,6 +12,7 @@ import { OVERFLOW_HIDDEN, SPACING, LegacyStyledText, + CURSOR_POINTER, } from '@opentrons/components' import { formatInterval } from '../RunTimeControl/utils' import { formatTimestamp } from './utils' @@ -125,7 +126,7 @@ export function HistoricalProtocolRun( {props.completeText} @@ -495,7 +496,7 @@ function StepRightElement(props: StepRightElementProps): JSX.Element | null { css={TYPOGRAPHY.pSemiBold} marginRight={SPACING.spacing16} id={`RunSetupCard_${props.stepKey}_missingHardwareText`} - whitespace="nowrap" + whitespace={NO_WRAP} > {props.missingHardwareText} @@ -516,7 +517,7 @@ function StepRightElement(props: StepRightElementProps): JSX.Element | null { css={TYPOGRAPHY.pSemiBold} marginRight={SPACING.spacing16} id={`RunSetupCard_${props.stepKey}_incompleteText`} - whitespace="nowrap" + whitespace={NO_WRAP} > {props.incompleteText} @@ -537,7 +538,7 @@ function LearnAboutLPC(): JSX.Element { { // clicking link shouldn't toggle step expanded state e.preventDefault() diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/LiquidDetailCard.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLiquids/LiquidDetailCard.tsx index 60044410e76..4bd3aad0b38 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/LiquidDetailCard.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupLiquids/LiquidDetailCard.tsx @@ -6,15 +6,16 @@ import { BORDERS, Box, COLORS, + CURSOR_POINTER, DIRECTION_COLUMN, DIRECTION_ROW, Flex, Icon, JUSTIFY_CENTER, JUSTIFY_SPACE_BETWEEN, + LegacyStyledText, SIZE_1, SPACING, - LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' import { MICRO_LITERS } from '@opentrons/shared-data' @@ -41,7 +42,7 @@ const LIQUID_CARD_STYLE = css` &:hover { border: 1px solid ${COLORS.grey60}; border-radius: ${BORDERS.borderRadius8}; - cursor: pointer; + cursor: ${CURSOR_POINTER}; } ` const LIQUID_CARD_ODD_STYLE = css` diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/SetupLiquidsList.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLiquids/SetupLiquidsList.tsx index 34aba75a1e5..7b90074f849 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/SetupLiquidsList.tsx +++ b/app/src/organisms/Devices/ProtocolRun/SetupLiquids/SetupLiquidsList.tsx @@ -6,8 +6,9 @@ import { ALIGN_CENTER, BORDERS, Box, - DeckInfoLabel, COLORS, + CURSOR_POINTER, + DeckInfoLabel, DIRECTION_COLUMN, DIRECTION_ROW, Flex, @@ -149,14 +150,14 @@ export function LiquidsListItem(props: LiquidsListItemProps): JSX.Element { ${CARD_OUTLINE_BORDER_STYLE} &:hover { - cursor: pointer; + cursor: ${CURSOR_POINTER}; border: 1px solid ${COLORS.grey35}; } ` const LIQUID_CARD_ITEM_STYLE = css` border: 1px solid ${COLORS.white}; &:hover { - cursor: pointer; + cursor: ${CURSOR_POINTER}; border: 1px solid ${COLORS.grey30}; } ` diff --git a/app/src/organisms/Devices/RobotOverflowMenu.tsx b/app/src/organisms/Devices/RobotOverflowMenu.tsx index 35363f2d16b..6522897e77f 100644 --- a/app/src/organisms/Devices/RobotOverflowMenu.tsx +++ b/app/src/organisms/Devices/RobotOverflowMenu.tsx @@ -12,6 +12,7 @@ import { DIRECTION_COLUMN, Flex, MenuItem, + NO_WRAP, OverflowBtn, POSITION_ABSOLUTE, POSITION_RELATIVE, @@ -172,7 +173,7 @@ export function RobotOverflowMenu(props: RobotOverflowMenuProps): JSX.Element { /> {showOverflowMenu && !showConnectionTroubleshootingModal ? ( {showOverflowMenu ? ( {t('view_error_details')} diff --git a/app/src/organisms/GripperCard/index.tsx b/app/src/organisms/GripperCard/index.tsx index 389ce296df4..df2ef99fbb5 100644 --- a/app/src/organisms/GripperCard/index.tsx +++ b/app/src/organisms/GripperCard/index.tsx @@ -1,7 +1,12 @@ import * as React from 'react' import { Trans, useTranslation } from 'react-i18next' import { css } from 'styled-components' -import { SPACING, TYPOGRAPHY, LegacyStyledText } from '@opentrons/components' +import { + CURSOR_POINTER, + LegacyStyledText, + SPACING, + TYPOGRAPHY, +} from '@opentrons/components' import { getGripperDisplayName } from '@opentrons/shared-data' import { useCurrentSubsystemUpdateQuery } from '@opentrons/react-api-client' import { Banner } from '../../atoms/Banner' @@ -22,7 +27,7 @@ interface GripperCardProps { } const BANNER_LINK_CSS = css` text-decoration: ${TYPOGRAPHY.textDecorationUnderline}; - cursor: pointer; + cursor: ${CURSOR_POINTER}; margin-left: ${SPACING.spacing8}; ` diff --git a/app/src/organisms/LabwareCard/CustomLabwareOverflowMenu.tsx b/app/src/organisms/LabwareCard/CustomLabwareOverflowMenu.tsx index 5c80bdd2ec6..d0634f3282e 100644 --- a/app/src/organisms/LabwareCard/CustomLabwareOverflowMenu.tsx +++ b/app/src/organisms/LabwareCard/CustomLabwareOverflowMenu.tsx @@ -19,6 +19,7 @@ import { LegacyStyledText, MenuItem, Modal, + NO_WRAP, OverflowBtn, POSITION_ABSOLUTE, POSITION_RELATIVE, @@ -118,7 +119,7 @@ export function CustomLabwareOverflowMenu( top={SPACING.spacing32} right={0} flexDirection={DIRECTION_COLUMN} - whiteSpace="nowrap" + whiteSpace={NO_WRAP} > {t('show_in_folder')} diff --git a/app/src/organisms/ModuleCard/ModuleOverflowMenu.tsx b/app/src/organisms/ModuleCard/ModuleOverflowMenu.tsx index 78596b831de..8da1db06d8f 100644 --- a/app/src/organisms/ModuleCard/ModuleOverflowMenu.tsx +++ b/app/src/organisms/ModuleCard/ModuleOverflowMenu.tsx @@ -5,6 +5,7 @@ import { Flex, MenuItem, MenuList, + NO_WRAP, POSITION_RELATIVE, Tooltip, useHoverTooltip, @@ -151,7 +152,7 @@ export const ModuleOverflowMenu = ( item.onClick(item.isSecondary)} disabled={item.disabledReason || isDisabled} - whiteSpace="nowrap" + whiteSpace={NO_WRAP} > {item.setSetting} diff --git a/app/src/organisms/ModuleCard/hooks.tsx b/app/src/organisms/ModuleCard/hooks.tsx index 2a8ef8f1dc4..43ad848d2ae 100644 --- a/app/src/organisms/ModuleCard/hooks.tsx +++ b/app/src/organisms/ModuleCard/hooks.tsx @@ -1,7 +1,12 @@ import * as React from 'react' import { useCreateLiveCommandMutation } from '@opentrons/react-api-client' import { useTranslation } from 'react-i18next' -import { MenuItem, Tooltip, useHoverTooltip } from '@opentrons/components' +import { + MenuItem, + NO_WRAP, + Tooltip, + useHoverTooltip, +} from '@opentrons/components' import { HEATERSHAKER_MODULE_TYPE, MAGNETIC_MODULE_TYPE, @@ -153,7 +158,7 @@ export function useModuleOverflowMenu( onClick={() => { handleInstructionsClick() }} - whiteSpace="nowrap" + whiteSpace={NO_WRAP} > {t('heater_shaker:show_attachment_instructions')} @@ -244,7 +249,7 @@ export function useModuleOverflowMenu( key={`thermocycler_block_temp_command_btn_${String(module.moduleModel)}`} onClick={sendBlockTempCommand} disabled={isDisabled} - whiteSpace="nowrap" + whiteSpace={NO_WRAP} > {module.data.status !== 'idle' ? t('overflow_menu_deactivate_block') diff --git a/app/src/organisms/Navigation/index.tsx b/app/src/organisms/Navigation/index.tsx index e03a5f443d0..8341e175682 100644 --- a/app/src/organisms/Navigation/index.tsx +++ b/app/src/organisms/Navigation/index.tsx @@ -5,25 +5,26 @@ import { useLocation, NavLink } from 'react-router-dom' import styled from 'styled-components' import { - Icon, - Flex, + ALIGN_CENTER, + ALIGN_FLEX_START, + BORDERS, Box, COLORS, - SPACING, - DIRECTION_ROW, - ALIGN_CENTER, - JUSTIFY_SPACE_BETWEEN, - truncateString, - TYPOGRAPHY, + CURSOR_DEFAULT, DIRECTION_COLUMN, + DIRECTION_ROW, DISPLAY_FLEX, - ALIGN_FLEX_START, - POSITION_STICKY, + Flex, + Icon, + JUSTIFY_SPACE_BETWEEN, + OVERFLOW_SCROLL, POSITION_ABSOLUTE, POSITION_STATIC, - BORDERS, + POSITION_STICKY, RESPONSIVENESS, - OVERFLOW_SCROLL, + SPACING, + truncateString, + TYPOGRAPHY, } from '@opentrons/components' import { ODD_FOCUS_VISIBLE } from '../../atoms/buttons/constants' @@ -242,7 +243,7 @@ const TouchNavLink = styled(NavLink)` } @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - cursor: default; + cursor: ${CURSOR_DEFAULT}; } ` @@ -265,6 +266,6 @@ const IconButton = styled('button')` background-color: transparent; } @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - cursor: default; + cursor: ${CURSOR_DEFAULT}; } ` diff --git a/app/src/organisms/ODD/RobotSettingsDashboard/RobotSettingButton.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/RobotSettingButton.tsx index d8390ee7941..da16402f0e2 100644 --- a/app/src/organisms/ODD/RobotSettingsDashboard/RobotSettingButton.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/RobotSettingButton.tsx @@ -14,9 +14,10 @@ import { Icon, JUSTIFY_CENTER, JUSTIFY_SPACE_BETWEEN, + LegacyStyledText, + NO_WRAP, SPACING, TYPOGRAPHY, - LegacyStyledText, } from '@opentrons/components' import type { IconName } from '@opentrons/components' @@ -65,7 +66,7 @@ export function RobotSettingButton({ gridGap={SPACING.spacing24} alignItems={ALIGN_CENTER} width="100%" - whiteSpace="nowrap" + whiteSpace={NO_WRAP} > {iconName != null ? ( diff --git a/app/src/organisms/ODD/RobotSettingsDashboard/UpdateChannel.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/UpdateChannel.tsx index d1fa185bb32..d2049ef246d 100644 --- a/app/src/organisms/ODD/RobotSettingsDashboard/UpdateChannel.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/UpdateChannel.tsx @@ -11,6 +11,7 @@ import { SPACING, LegacyStyledText, TYPOGRAPHY, + CURSOR_POINTER, } from '@opentrons/components' import { ChildNavigation } from '../../../organisms/ChildNavigation' @@ -34,7 +35,7 @@ const SettingButton = styled.input` const SettingButtonLabel = styled.label` padding: ${SPACING.spacing24}; border-radius: ${BORDERS.borderRadius16}; - cursor: pointer; + cursor: ${CURSOR_POINTER}; background: ${({ isSelected }) => isSelected === true ? COLORS.blue50 : COLORS.blue35}; color: ${({ isSelected }) => isSelected === true && COLORS.white}; diff --git a/app/src/organisms/PipetteWizardFlows/ChoosePipette.tsx b/app/src/organisms/PipetteWizardFlows/ChoosePipette.tsx index a31076bea9d..fdf68a337c1 100644 --- a/app/src/organisms/PipetteWizardFlows/ChoosePipette.tsx +++ b/app/src/organisms/PipetteWizardFlows/ChoosePipette.tsx @@ -9,6 +9,7 @@ import { ALIGN_FLEX_END, BORDERS, COLORS, + CURSOR_POINTER, DIRECTION_COLUMN, DIRECTION_ROW, Flex, @@ -18,12 +19,12 @@ import { JUSTIFY_SPACE_AROUND, JUSTIFY_SPACE_BETWEEN, LegacyStyledText, + ModalShell, POSITION_ABSOLUTE, PrimaryButton, RESPONSIVENESS, SPACING, TYPOGRAPHY, - ModalShell, } from '@opentrons/components' import { EIGHT_CHANNEL, @@ -55,7 +56,7 @@ const UNSELECTED_OPTIONS_STYLE = css` border-radius: ${BORDERS.borderRadius8}; height: 14.5625rem; width: 14.5625rem; - cursor: pointer; + cursor: ${CURSOR_POINTER}; flex-direction: ${DIRECTION_COLUMN}; justify-content: ${JUSTIFY_CENTER}; align-items: ${ALIGN_CENTER}; diff --git a/app/src/organisms/ProtocolDetails/ProtocolLabwareDetails.tsx b/app/src/organisms/ProtocolDetails/ProtocolLabwareDetails.tsx index 59b4590dc7f..b2270f6552e 100644 --- a/app/src/organisms/ProtocolDetails/ProtocolLabwareDetails.tsx +++ b/app/src/organisms/ProtocolDetails/ProtocolLabwareDetails.tsx @@ -10,6 +10,7 @@ import { Icon, InfoScreen, MenuItem, + NO_WRAP, OverflowBtn, POSITION_ABSOLUTE, POSITION_RELATIVE, @@ -182,7 +183,7 @@ export const LabwareDetailOverflowMenu = ( {showOverflowMenu ? ( {showOverflowMenu ? ( - + {formatTimeWithUtcLabel(protocol.createdAt)} diff --git a/app/src/pages/ODD/QuickTransferDashboard/QuickTransferCard.tsx b/app/src/pages/ODD/QuickTransferDashboard/QuickTransferCard.tsx index 23d593d0933..389566583cc 100644 --- a/app/src/pages/ODD/QuickTransferDashboard/QuickTransferCard.tsx +++ b/app/src/pages/ODD/QuickTransferDashboard/QuickTransferCard.tsx @@ -15,13 +15,14 @@ import { Flex, Icon, JUSTIFY_SPACE_BETWEEN, + LegacyStyledText, + NO_WRAP, + OVERFLOW_HIDDEN, OVERFLOW_WRAP_ANYWHERE, OVERFLOW_WRAP_BREAK_WORD, SPACING, - LegacyStyledText, TYPOGRAPHY, useLongPress, - OVERFLOW_HIDDEN, } from '@opentrons/components' import { useHost, @@ -221,7 +222,7 @@ export function QuickTransferCard(props: { {transferName} - + {formatTimeWithUtcLabel(quickTransfer.createdAt)} diff --git a/components/src/atoms/Checkbox/index.tsx b/components/src/atoms/Checkbox/index.tsx index da976978728..58da8c1fe4b 100644 --- a/components/src/atoms/Checkbox/index.tsx +++ b/components/src/atoms/Checkbox/index.tsx @@ -5,6 +5,8 @@ import { Flex } from '../../primitives' import { Icon } from '../../icons' import { ALIGN_CENTER, + CURSOR_AUTO, + CURSOR_POINTER, DIRECTION_ROW, FLEX_MAX_CONTENT, JUSTIFY_SPACE_BETWEEN, @@ -46,7 +48,7 @@ export function Checkbox(props: CheckboxProps): JSX.Element { border-radius: ${BORDERS.borderRadiusFull}; padding: ${SPACING.spacing12} ${SPACING.spacing16}; justify-content: ${JUSTIFY_SPACE_BETWEEN}; - cursor: pointer; + cursor: ${CURSOR_POINTER}; &:active { background-color: ${isChecked ? COLORS.blue55 : COLORS.blue40}; @@ -65,7 +67,7 @@ export function Checkbox(props: CheckboxProps): JSX.Element { padding: ${SPACING.spacing20}; border-radius: ${BORDERS.borderRadius16}; width: 100%; - cursor: auto; + cursor: ${CURSOR_AUTO}; } ` diff --git a/components/src/atoms/CheckboxField/index.tsx b/components/src/atoms/CheckboxField/index.tsx index f416a771186..ae7134fcbb5 100644 --- a/components/src/atoms/CheckboxField/index.tsx +++ b/components/src/atoms/CheckboxField/index.tsx @@ -4,7 +4,7 @@ import { SPACING, TYPOGRAPHY } from '../../ui-style-constants' import { COLORS, BORDERS } from '../../helix-design-system' import { Flex, Box } from '../../primitives' import { Icon } from '../../icons' -import { ALIGN_CENTER, JUSTIFY_CENTER } from '../../styles' +import { ALIGN_CENTER, CURSOR_POINTER, JUSTIFY_CENTER } from '../../styles' export interface CheckboxFieldProps { /** change handler */ @@ -55,7 +55,7 @@ const INNER_STYLE_VALUE = css` align-items: ${ALIGN_CENTER}; &:hover { - cursor: pointer; + cursor: ${CURSOR_POINTER}; color: ${COLORS.blue55}; } @@ -81,7 +81,7 @@ const INNER_STYLE_NO_VALUE = css` align-items: ${ALIGN_CENTER}; &:hover { - cursor: pointer; + cursor: ${CURSOR_POINTER}; color: ${COLORS.grey60}; } diff --git a/components/src/atoms/ListButton/ListButtonChildren/ListButtonRadioButton.tsx b/components/src/atoms/ListButton/ListButtonChildren/ListButtonRadioButton.tsx index bbcc98e12d5..8114f598fe9 100644 --- a/components/src/atoms/ListButton/ListButtonChildren/ListButtonRadioButton.tsx +++ b/components/src/atoms/ListButton/ListButtonChildren/ListButtonRadioButton.tsx @@ -4,6 +4,7 @@ import { SPACING } from '../../../ui-style-constants' import { BORDERS, COLORS } from '../../../helix-design-system' import { Flex } from '../../../primitives' import { StyledText } from '../../StyledText' +import { CURSOR_POINTER } from '../../../styles' import type { StyleProps } from '../../../primitives' @@ -62,7 +63,7 @@ export function ListButtonRadioButton( const SettingButtonLabel = styled.label` border-radius: ${BORDERS.borderRadius8}; - cursor: pointer; + cursor: ${CURSOR_POINTER}; padding: 14px ${SPACING.spacing12}; width: 100%; diff --git a/components/src/atoms/ListButton/index.tsx b/components/src/atoms/ListButton/index.tsx index 3445254949e..ab6cb36eb60 100644 --- a/components/src/atoms/ListButton/index.tsx +++ b/components/src/atoms/ListButton/index.tsx @@ -3,6 +3,7 @@ import { css } from 'styled-components' import { Flex } from '../../primitives' import { SPACING } from '../../ui-style-constants' import { BORDERS, COLORS } from '../../helix-design-system' +import { CURSOR_POINTER } from '../../styles' import type { StyleProps } from '../../primitives' export * from './ListButtonChildren/index' @@ -44,7 +45,7 @@ export function ListButton(props: ListButtonProps): JSX.Element { const listButtonProps = LISTBUTTON_PROPS_BY_TYPE[type] const LIST_BUTTON_STYLE = css` - cursor: pointer; + cursor: ${CURSOR_POINTER}; background-color: ${disabled ? COLORS.grey35 : listButtonProps.backgroundColor}; diff --git a/components/src/atoms/Toast/index.tsx b/components/src/atoms/Toast/index.tsx index a80fa75fba8..2e6c05dabaa 100644 --- a/components/src/atoms/Toast/index.tsx +++ b/components/src/atoms/Toast/index.tsx @@ -11,6 +11,7 @@ import { DIRECTION_COLUMN, DIRECTION_ROW, JUSTIFY_SPACE_BETWEEN, + NO_WRAP, } from '../../styles' import { LegacyStyledText } from '../StyledText' import type { @@ -362,7 +363,7 @@ export function Toast(props: ToastProps): JSX.Element { maxWidth={showODDStyle ? '30.375rem' : 'auto'} overflow="hidden" textOverflow="ellipsis" - whiteSpace="nowrap" + whiteSpace={NO_WRAP} > {headingText} @@ -412,7 +413,7 @@ export function Toast(props: ToastProps): JSX.Element { showODDStyle ? 'none' : TYPOGRAPHY.textDecorationUnderline } textTransform={TYPOGRAPHY.textTransformCapitalize} - whiteSpace="nowrap" + whiteSpace={NO_WRAP} > {closeText} diff --git a/components/src/atoms/buttons/LargeButton.tsx b/components/src/atoms/buttons/LargeButton.tsx index 9476a7d693d..55904cdf57f 100644 --- a/components/src/atoms/buttons/LargeButton.tsx +++ b/components/src/atoms/buttons/LargeButton.tsx @@ -9,6 +9,8 @@ import { fontSizeBodyLargeSemiBold } from '../../helix-design-system/product/typ import { ALIGN_CENTER, ALIGN_FLEX_START, + CURSOR_DEFAULT, + CURSOR_POINTER, DIRECTION_COLUMN, DISPLAY_FLEX, JUSTIFY_SPACE_BETWEEN, @@ -158,7 +160,7 @@ export function LargeButton(props: LargeButtonProps): JSX.Element { background-color: ${ LARGE_BUTTON_PROPS_BY_TYPE[buttonType].defaultBackgroundColor }; - cursor: pointer; + cursor: ${CURSOR_POINTER}; padding: ${SPACING.spacing16} ${SPACING.spacing24}; text-align: ${TYPOGRAPHY.textAlignCenter}; border-radius: ${BORDERS.borderRadiusFull}; @@ -205,7 +207,7 @@ export function LargeButton(props: LargeButtonProps): JSX.Element { } @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - cursor: default; + cursor: ${CURSOR_DEFAULT}; align-items: ${ALIGN_FLEX_START}; flex-direction: ${DIRECTION_COLUMN}; border-radius: ${BORDERS.borderRadius16}; diff --git a/components/src/atoms/buttons/RadioButton.tsx b/components/src/atoms/buttons/RadioButton.tsx index d9a9502d6c7..e2a32c72b4d 100644 --- a/components/src/atoms/buttons/RadioButton.tsx +++ b/components/src/atoms/buttons/RadioButton.tsx @@ -5,6 +5,9 @@ import { ALIGN_CENTER, BORDERS, COLORS, + CURSOR_DEFAULT, + CURSOR_NOT_ALLOWED, + CURSOR_POINTER, DIRECTION_ROW, Icon, RESPONSIVENESS, @@ -80,14 +83,14 @@ export function RadioButton(props: RadioButtonProps): JSX.Element { const DISABLED_BUTTON_STYLE = css` background-color: ${COLORS.grey35}; color: ${COLORS.grey50}; - cursor: not-allowed; + cursor: ${CURSOR_NOT_ALLOWED}; ` const SettingButtonLabel = styled.label` border-radius: ${ !largeDesktopBorderRadius ? BORDERS.borderRadius40 : BORDERS.borderRadius8 }; - cursor: pointer; + cursor: ${CURSOR_POINTER}; padding: ${SPACING.spacing12} ${SPACING.spacing16}; width: 100%; @@ -99,7 +102,7 @@ export function RadioButton(props: RadioButtonProps): JSX.Element { } @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - cursor: default; + cursor: ${CURSOR_DEFAULT}; padding: ${isLarge ? SPACING.spacing24 : SPACING.spacing20}; border-radius: ${BORDERS.borderRadius16}; display: ${maxLines != null ? '-webkit-box' : undefined}; diff --git a/components/src/atoms/buttons/SecondaryButton.tsx b/components/src/atoms/buttons/SecondaryButton.tsx index a7620495d5c..f999866f9d3 100644 --- a/components/src/atoms/buttons/SecondaryButton.tsx +++ b/components/src/atoms/buttons/SecondaryButton.tsx @@ -3,7 +3,7 @@ import styled from 'styled-components' import { TYPOGRAPHY, SPACING } from '../../ui-style-constants' import { isntStyleProp, styleProps } from '../../primitives' import { BORDERS, COLORS } from '../../helix-design-system' - +import { CURSOR_DEFAULT, CURSOR_POINTER } from '../../index' import type { StyleProps } from '../../index' interface SecondaryButtonProps extends StyleProps { @@ -14,7 +14,7 @@ export const SecondaryButton = styled.button.withConfig({ shouldForwardProp: p => isntStyleProp(p) && p !== 'isDangerous', })` appearance: none; - cursor: pointer; + cursor: ${CURSOR_POINTER}; color: ${props => (props.isDangerous ? COLORS.red50 : COLORS.blue50)}; border: ${BORDERS.lineBorder}; border-color: ${props => (props.isDangerous ? COLORS.red50 : 'initial')}; @@ -55,7 +55,7 @@ export const SecondaryButton = styled.button.withConfig({ box-shadow: none; border-color: ${COLORS.grey30}; color: ${COLORS.grey40}; - cursor: default; + cursor: ${CURSOR_DEFAULT}; } ${styleProps} diff --git a/components/src/modals/ModalShell.tsx b/components/src/modals/ModalShell.tsx index a1a10035be5..27007d0ea89 100644 --- a/components/src/modals/ModalShell.tsx +++ b/components/src/modals/ModalShell.tsx @@ -2,6 +2,7 @@ import * as React from 'react' import styled from 'styled-components' import { ALIGN_CENTER, + CURSOR_DEFAULT, JUSTIFY_CENTER, OVERFLOW_AUTO, POSITION_ABSOLUTE, @@ -82,7 +83,7 @@ const Overlay = styled.div` bottom: 0; z-index: 1; background-color: ${COLORS.black90}${COLORS.opacity40HexCode}; - cursor: default; + cursor: ${CURSOR_DEFAULT}; ` const ContentArea = styled.div<{ zIndex: string | number }>` display: flex; diff --git a/components/src/molecules/DropdownMenu/index.tsx b/components/src/molecules/DropdownMenu/index.tsx index 3034cb33f3c..924d554cd0d 100644 --- a/components/src/molecules/DropdownMenu/index.tsx +++ b/components/src/molecules/DropdownMenu/index.tsx @@ -4,6 +4,7 @@ import { css } from 'styled-components' import { BORDERS, COLORS } from '../../helix-design-system' import { ALIGN_CENTER, + CURSOR_POINTER, DIRECTION_COLUMN, DIRECTION_ROW, JUSTIFY_SPACE_BETWEEN, @@ -136,7 +137,7 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element { const DROPDOWN_STYLE = css` flex-direction: ${DIRECTION_ROW}; background-color: ${COLORS.white}; - cursor: pointer; + cursor: ${CURSOR_POINTER}; padding: ${SPACING.spacing8} ${SPACING.spacing12}; border: 1px ${BORDERS.styleSolid} ${showDropdownMenu ? COLORS.blue50 : COLORS.grey50}; diff --git a/components/src/primitives/Btn.tsx b/components/src/primitives/Btn.tsx index 5ea917a3a29..03246c7208f 100644 --- a/components/src/primitives/Btn.tsx +++ b/components/src/primitives/Btn.tsx @@ -17,15 +17,15 @@ const BUTTON_BASE_STYLE = css` border-width: 0; border-style: solid; background-color: transparent; - cursor: pointer; + cursor: ${Styles.CURSOR_POINTER}; &:disabled, &.disabled { - cursor: default; + cursor: ${Styles.CURSOR_DEFAULT}; } @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - cursor: default; + cursor: ${Styles.CURSOR_DEFAULT}; } ` diff --git a/components/src/primitives/Link.tsx b/components/src/primitives/Link.tsx index c2bf28e10a0..06c0ca50b2e 100644 --- a/components/src/primitives/Link.tsx +++ b/components/src/primitives/Link.tsx @@ -3,6 +3,7 @@ import styled from 'styled-components' import { styleProps, isntStyleProp } from './style-props' import type { StyleProps, PrimitiveComponent } from './types' +import { CURSOR_POINTER } from '../styles' export interface LinkProps extends StyleProps { /** render link with target="_blank" */ @@ -26,6 +27,6 @@ export const Link: PrimitiveComponent<'a', LinkProps> = styled.a } )` text-decoration: none; - cursor: pointer; + cursor: ${CURSOR_POINTER}; ${styleProps} ` diff --git a/components/src/primitives/style-props.ts b/components/src/primitives/style-props.ts index b7de00e2513..c6c0710e83f 100644 --- a/components/src/primitives/style-props.ts +++ b/components/src/primitives/style-props.ts @@ -54,6 +54,7 @@ const FLEXBOX_PROPS = [ 'alignItems', 'justifyContent', 'flexDirection', + 'flexGrow', 'flexWrap', 'alignSelf', 'whiteSpace', diff --git a/components/src/styles/cursor.ts b/components/src/styles/cursor.ts new file mode 100644 index 00000000000..f4d2b0643ba --- /dev/null +++ b/components/src/styles/cursor.ts @@ -0,0 +1,12 @@ +// cursor +// https://developer.mozilla.org/en-US/docs/Web/CSS/cursor#keyword +export const CURSOR_AUTO = 'auto' +export const CURSOR_CROSSHAIR = 'crosshair' +export const CURSOR_DEFAULT = 'default' +export const CURSOR_GRAB = 'grab' +export const CURSOR_GRABBING = 'grabbing' +export const CURSOR_HELP = 'help' +export const CURSOR_NOT_ALLOWED = 'not-allowed' +export const CURSOR_POINTER = 'pointer' +export const CURSOR_ZOOM_IN = 'zoom-in' +export const CURSOR_WAIT = 'wait' diff --git a/components/src/styles/flexbox.ts b/components/src/styles/flexbox.ts index 2c36936b200..4ee6286b204 100644 --- a/components/src/styles/flexbox.ts +++ b/components/src/styles/flexbox.ts @@ -31,3 +31,9 @@ export const DIRECTION_COLUMN_REVERSE = 'column-reverse' export const WRAP = 'wrap' export const NO_WRAP = 'nowrap' export const WRAP_REVERSE = 'wrap-reverse' + +export const WHITE_SPACE_NORMAL = 'normal' +export const WHITE_SPACE_PRE = 'pre' +export const WHITE_SPACE_PRE_WRAP = 'pre-wrap' +export const WHITE_SPACE_PRE_LINE = 'pre-line' +export const WHITE_SPACE_BREAK_SPACES = 'break-spaces' diff --git a/components/src/styles/index.ts b/components/src/styles/index.ts index aa5ec49761c..83ba2196a3a 100644 --- a/components/src/styles/index.ts +++ b/components/src/styles/index.ts @@ -1,5 +1,6 @@ export * from './borders' export * from './colors' +export * from './cursor' export * from './flexbox' export * from './layout' export * from './position' diff --git a/components/src/tooltips/LegacyTooltip.tsx b/components/src/tooltips/LegacyTooltip.tsx index dac98997f39..b0ea0c32ae2 100644 --- a/components/src/tooltips/LegacyTooltip.tsx +++ b/components/src/tooltips/LegacyTooltip.tsx @@ -6,6 +6,7 @@ import { fontSizeH4 } from '../ui-style-constants/typography' import { spacing8 } from '../ui-style-constants/spacing' import { ARROW_SIZE_PX } from './styles' import { Box } from '../primitives' +import { CURSOR_POINTER } from '../styles' import type { CSSProperties } from 'react' import type { FlattenSimpleInterpolation } from 'styled-components' @@ -60,7 +61,7 @@ export const LegacyTooltip = React.forwardRef(function TooltipComponent( padding: ${spacing8}; color: ${COLORS.white}; filter: drop-shadow(0px 1px 3px rgba(0, 0, 0, 0.2)); - cursor: pointer; + cursor: ${CURSOR_POINTER}; font-size: ${fontSize}; border-radius: ${BORDERS.borderRadius4}; ` diff --git a/opentrons-ai-client/src/organisms/MainContentContainer/index.tsx b/opentrons-ai-client/src/organisms/MainContentContainer/index.tsx index 79a989417fa..6cd42e698c3 100644 --- a/opentrons-ai-client/src/organisms/MainContentContainer/index.tsx +++ b/opentrons-ai-client/src/organisms/MainContentContainer/index.tsx @@ -1,6 +1,6 @@ import React from 'react' import { useTranslation } from 'react-i18next' -import styled, { css } from 'styled-components' +import styled from 'styled-components' import { useAtom } from 'jotai' import { @@ -45,9 +45,7 @@ export function MainContentContainer(): JSX.Element { width="100%" overflowY={OVERFLOW_AUTO} flexDirection={DIRECTION_COLUMN} - css={css` - flex-grow: 1; - `} + flexGrow="1" > {/* Prompt Guide remain as a reference for users. */} diff --git a/protocol-designer/src/NavigationBar.tsx b/protocol-designer/src/NavigationBar.tsx index 182bda62c51..2099f41f957 100644 --- a/protocol-designer/src/NavigationBar.tsx +++ b/protocol-designer/src/NavigationBar.tsx @@ -8,6 +8,7 @@ import { ALIGN_CENTER, Btn, COLORS, + CURSOR_POINTER, DIRECTION_COLUMN, Flex, JUSTIFY_SPACE_BETWEEN, @@ -87,7 +88,7 @@ export function NavigationBar(): JSX.Element | null { const StyledLabel = styled.label` height: 20px; - cursor: pointer; + cursor: ${CURSOR_POINTER}; input[type='file'] { display: none; } diff --git a/protocol-designer/src/components/ComputingSpinner.tsx b/protocol-designer/src/components/ComputingSpinner.tsx index 453bb4cd6e1..921295f27a5 100644 --- a/protocol-designer/src/components/ComputingSpinner.tsx +++ b/protocol-designer/src/components/ComputingSpinner.tsx @@ -1,11 +1,11 @@ import * as React from 'react' import { useSelector } from 'react-redux' import { css } from 'styled-components' -import { Box, POSITION_FIXED } from '@opentrons/components' +import { Box, CURSOR_WAIT, POSITION_FIXED } from '@opentrons/components' import * as fileDataSelectors from '../file-data/selectors' const waitCursorStyle = css` - cursor: wait; + cursor: ${CURSOR_WAIT}; ` export const ComputingSpinner = (): JSX.Element | null => { diff --git a/protocol-designer/src/components/modals/CreateFileWizard/EquipmentOption.tsx b/protocol-designer/src/components/modals/CreateFileWizard/EquipmentOption.tsx index b8adad778a4..e1852fe4e03 100644 --- a/protocol-designer/src/components/modals/CreateFileWizard/EquipmentOption.tsx +++ b/protocol-designer/src/components/modals/CreateFileWizard/EquipmentOption.tsx @@ -8,20 +8,21 @@ import { } from '@opentrons/shared-data' import { - Flex, - Text, - Icon, - SPACING, ALIGN_CENTER, BORDERS, - JUSTIFY_CENTER, + Box, COLORS, - TYPOGRAPHY, - useHoverTooltip, - LegacyTooltip, + CURSOR_POINTER, DIRECTION_COLUMN, - Box, + Flex, + Icon, + JUSTIFY_CENTER, LegacyStyledText, + LegacyTooltip, + SPACING, + Text, + TYPOGRAPHY, + useHoverTooltip, } from '@opentrons/components' import { MAX_MAGNETIC_BLOCKS, MAX_MOAM_MODULES } from './ModulesAndOtherTile' import type { StyleProps } from '@opentrons/components' @@ -29,7 +30,7 @@ import type { ModuleType, RobotType } from '@opentrons/shared-data' const ARROW_STYLE = css` color: ${COLORS.grey50}; - cursor: pointer; + cursor: ${CURSOR_POINTER}; &:hover { color: ${COLORS.black80}; } @@ -37,7 +38,7 @@ const ARROW_STYLE = css` const ARROW_STYLE_ACTIVE = css` color: ${COLORS.blue50}; - cursor: pointer; + cursor: ${CURSOR_POINTER}; &:hover { color: ${COLORS.black80}; } diff --git a/protocol-designer/src/components/modals/CreateFileWizard/RobotTypeTile.tsx b/protocol-designer/src/components/modals/CreateFileWizard/RobotTypeTile.tsx index 8e683cd3d8a..9f54d117ff3 100644 --- a/protocol-designer/src/components/modals/CreateFileWizard/RobotTypeTile.tsx +++ b/protocol-designer/src/components/modals/CreateFileWizard/RobotTypeTile.tsx @@ -2,21 +2,22 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' import { + ALIGN_CENTER, + BORDERS, + COLORS, + CURSOR_POINTER, DIRECTION_COLUMN, + DIRECTION_ROW, Flex, - SPACING, - TYPOGRAPHY, - Text, - COLORS, - BORDERS, JUSTIFY_CENTER, - ALIGN_CENTER, - RESPONSIVENESS, - DIRECTION_ROW, + JUSTIFY_FLEX_END, JUSTIFY_FLEX_START, JUSTIFY_SPACE_BETWEEN, PrimaryButton, - JUSTIFY_FLEX_END, + RESPONSIVENESS, + SPACING, + Text, + TYPOGRAPHY, } from '@opentrons/components' import { FLEX_DISPLAY_NAME, @@ -137,7 +138,7 @@ const UNSELECTED_OPTIONS_STYLE = css` border-radius: ${BORDERS.borderRadius8}; height: 14.5625rem; width: 14.5625rem; - cursor: pointer; + cursor: ${CURSOR_POINTER}; flex-direction: ${DIRECTION_COLUMN}; justify-content: ${JUSTIFY_CENTER}; align-items: ${ALIGN_CENTER}; diff --git a/protocol-designer/src/organisms/EditInstrumentsModal/index.tsx b/protocol-designer/src/organisms/EditInstrumentsModal/index.tsx index b77811de040..53146ed99f6 100644 --- a/protocol-designer/src/organisms/EditInstrumentsModal/index.tsx +++ b/protocol-designer/src/organisms/EditInstrumentsModal/index.tsx @@ -10,6 +10,7 @@ import { Btn, Checkbox, COLORS, + CURSOR_POINTER, DIRECTION_COLUMN, DIRECTION_ROW, DISPLAY_FLEX, @@ -583,7 +584,7 @@ const StyledLabel = styled.label` text-decoration: ${TYPOGRAPHY.textDecorationUnderline}; font-size: ${PRODUCT.TYPOGRAPHY.fontSizeBodyDefaultSemiBold}; display: ${DISPLAY_INLINE_BLOCK}; - cursor: pointer; + cursor: ${CURSOR_POINTER}; input[type='file'] { display: none; } diff --git a/protocol-designer/src/pages/CreateNewProtocolWizard/SelectPipettes.tsx b/protocol-designer/src/pages/CreateNewProtocolWizard/SelectPipettes.tsx index 6f95166d268..3986b2258a4 100644 --- a/protocol-designer/src/pages/CreateNewProtocolWizard/SelectPipettes.tsx +++ b/protocol-designer/src/pages/CreateNewProtocolWizard/SelectPipettes.tsx @@ -12,6 +12,7 @@ import { Box, Btn, Checkbox, + CURSOR_POINTER, DIRECTION_COLUMN, DIRECTION_ROW, DISPLAY_FLEX, @@ -494,7 +495,7 @@ const StyledLabel = styled.label` text-decoration: ${TYPOGRAPHY.textDecorationUnderline}; font-size: ${PRODUCT.TYPOGRAPHY.fontSizeBodyDefaultSemiBold}; display: ${DISPLAY_INLINE_BLOCK}; - cursor: pointer; + cursor: ${CURSOR_POINTER}; input[type='file'] { display: none; } diff --git a/protocol-designer/src/pages/Designer/DeckSetup/DeckItemHover.tsx b/protocol-designer/src/pages/Designer/DeckSetup/DeckItemHover.tsx index c67022e7d19..2dc111d85dd 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/DeckItemHover.tsx +++ b/protocol-designer/src/pages/Designer/DeckSetup/DeckItemHover.tsx @@ -6,6 +6,7 @@ import { ALIGN_CENTER, BORDERS, COLORS, + CURSOR_POINTER, DISPLAY_FLEX, Flex, JUSTIFY_CENTER, @@ -81,7 +82,7 @@ export function DeckItemHover(props: DeckItemHoverProps): JSX.Element | null { color: COLORS.white, fontSize: PRODUCT.TYPOGRAPHY.fontSizeBodyDefaultSemiBold, borderRadius: BORDERS.borderRadius8, - cursor: 'pointer', + cursor: CURSOR_POINTER, }, onMouseEnter: () => { setHover(itemId) diff --git a/protocol-designer/src/pages/Designer/DeckSetup/LabwareTools.tsx b/protocol-designer/src/pages/Designer/DeckSetup/LabwareTools.tsx index 9da8dfab291..dec0d114f83 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/LabwareTools.tsx +++ b/protocol-designer/src/pages/Designer/DeckSetup/LabwareTools.tsx @@ -6,6 +6,7 @@ import { useDispatch, useSelector } from 'react-redux' import { ALIGN_CENTER, CheckboxField, + CURSOR_POINTER, DIRECTION_COLUMN, DISPLAY_INLINE_BLOCK, Flex, @@ -477,7 +478,7 @@ const StyledLabel = styled.label` text-decoration: ${TYPOGRAPHY.textDecorationUnderline}; text-align: ${TYPOGRAPHY.textAlignCenter}; display: ${DISPLAY_INLINE_BLOCK}; - cursor: pointer; + cursor: ${CURSOR_POINTER}; input[type='file'] { display: none; } diff --git a/protocol-designer/src/pages/Designer/DeckSetup/SlotOverflowMenu.tsx b/protocol-designer/src/pages/Designer/DeckSetup/SlotOverflowMenu.tsx index 610683b014b..9f03734379d 100644 --- a/protocol-designer/src/pages/Designer/DeckSetup/SlotOverflowMenu.tsx +++ b/protocol-designer/src/pages/Designer/DeckSetup/SlotOverflowMenu.tsx @@ -6,6 +6,8 @@ import { useNavigate } from 'react-router-dom' import { BORDERS, COLORS, + CURSOR_AUTO, + CURSOR_POINTER, DIRECTION_COLUMN, Flex, NO_WRAP, @@ -275,7 +277,7 @@ export function SlotOverflowMenu( const MenuButton = styled.button` background-color: ${COLORS.transparent}; border-radius: inherit; - cursor: pointer; + cursor: ${CURSOR_POINTER}; padding: ${SPACING.spacing8} ${SPACING.spacing12}; border: none; border-radius: inherit; @@ -284,6 +286,6 @@ const MenuButton = styled.button` } &:disabled { color: ${COLORS.grey40}; - cursor: auto; + cursor: ${CURSOR_AUTO}; } ` diff --git a/protocol-designer/src/pages/Designer/LiquidsOverflowMenu.tsx b/protocol-designer/src/pages/Designer/LiquidsOverflowMenu.tsx index 6e36a3ccb19..fb992cab7f0 100644 --- a/protocol-designer/src/pages/Designer/LiquidsOverflowMenu.tsx +++ b/protocol-designer/src/pages/Designer/LiquidsOverflowMenu.tsx @@ -8,6 +8,8 @@ import { BORDERS, Box, COLORS, + CURSOR_AUTO, + CURSOR_POINTER, DIRECTION_COLUMN, Flex, Icon, @@ -97,7 +99,7 @@ export function LiquidsOverflowMenu( } const MenuButton = styled.button` background-color: ${COLORS.transparent}; - cursor: pointer; + cursor: ${CURSOR_POINTER}; padding: ${SPACING.spacing8} ${SPACING.spacing12}; border: none; border-radius: inherit; @@ -106,6 +108,6 @@ const MenuButton = styled.button` } &:disabled { color: ${COLORS.grey40}; - cursor: auto; + cursor: ${CURSOR_AUTO}; } ` diff --git a/protocol-designer/src/pages/Landing/index.tsx b/protocol-designer/src/pages/Landing/index.tsx index d0fe4acdbca..80c2d022d43 100644 --- a/protocol-designer/src/pages/Landing/index.tsx +++ b/protocol-designer/src/pages/Landing/index.tsx @@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next' import { ALIGN_CENTER, COLORS, + CURSOR_POINTER, DIRECTION_COLUMN, Flex, LargeButton, @@ -93,7 +94,7 @@ export function Landing(): JSX.Element { const StyledLabel = styled.label` display: inline-block; - cursor: pointer; + cursor: ${CURSOR_POINTER}; input[type='file'] { display: none; } diff --git a/protocol-designer/src/pages/ProtocolOverview/index.tsx b/protocol-designer/src/pages/ProtocolOverview/index.tsx index 6acf8d2e770..91b7d5d6367 100644 --- a/protocol-designer/src/pages/ProtocolOverview/index.tsx +++ b/protocol-designer/src/pages/ProtocolOverview/index.tsx @@ -20,12 +20,13 @@ import { ListItem, ListItemDescriptor, Modal, + NO_WRAP, PrimaryButton, - SPACING, SecondaryButton, + SPACING, StyledText, - TYPOGRAPHY, ToggleGroup, + TYPOGRAPHY, } from '@opentrons/components' import { getPipetteSpecsV2, @@ -316,7 +317,7 @@ export function ProtocolOverview(): JSX.Element { onClick={() => { navigate('/designer') }} - whiteSpace="nowrap" + whiteSpace={NO_WRAP} height="3.5rem" />