Skip to content

Commit

Permalink
fix(robot-server): Publish to maintenance_runs/current_run on initi…
Browse files Browse the repository at this point in the history
…al `PLAY` action (#15943)

When a client posts a PLAY action that starts a protocol run, any existing maintenance run ID is cleared from the server. This means that MQTT should update subscribers interested in the current maintenance run.
  • Loading branch information
mjhuff authored Aug 9, 2024
1 parent c6c151d commit 1b0afad
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ async def create(
state_summary=state_summary,
)

await self._maintenance_runs_publisher.publish_current_maintenance_run()
await self._maintenance_runs_publisher.publish_current_maintenance_run_async()

return maintenance_run_data

Expand Down Expand Up @@ -157,7 +157,7 @@ async def delete(self, run_id: str) -> None:
if run_id == self._run_orchestrator_store.current_run_id:
await self._run_orchestrator_store.clear()

await self._maintenance_runs_publisher.publish_current_maintenance_run()
await self._maintenance_runs_publisher.publish_current_maintenance_run_async()

else:
raise MaintenanceRunNotFoundError(run_id=run_id)
Expand Down
11 changes: 10 additions & 1 deletion robot-server/robot_server/runs/router/actions_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@
from robot_server.maintenance_runs.dependencies import (
get_maintenance_run_orchestrator_store,
)
from robot_server.service.notifications import get_runs_publisher, RunsPublisher
from robot_server.service.notifications import (
get_runs_publisher,
get_maintenance_runs_publisher,
RunsPublisher,
MaintenanceRunsPublisher,
)

log = logging.getLogger(__name__)
actions_router = APIRouter()
Expand All @@ -49,6 +54,9 @@ async def get_run_controller(
run_orchestrator_store: RunOrchestratorStore = Depends(get_run_orchestrator_store),
run_store: RunStore = Depends(get_run_store),
runs_publisher: RunsPublisher = Depends(get_runs_publisher),
maintenance_runs_publisher: MaintenanceRunsPublisher = Depends(
get_maintenance_runs_publisher
),
) -> RunController:
"""Get a RunController for the current run.
Expand All @@ -72,6 +80,7 @@ async def get_run_controller(
run_orchestrator_store=run_orchestrator_store,
run_store=run_store,
runs_publisher=runs_publisher,
maintenance_runs_publisher=maintenance_runs_publisher,
)


Expand Down
6 changes: 5 additions & 1 deletion robot-server/robot_server/runs/run_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from opentrons.protocol_engine.types import DeckConfigurationType

from robot_server.service.notifications import RunsPublisher
from robot_server.service.notifications import RunsPublisher, MaintenanceRunsPublisher

log = logging.getLogger(__name__)

Expand All @@ -32,12 +32,14 @@ def __init__(
run_orchestrator_store: RunOrchestratorStore,
run_store: RunStore,
runs_publisher: RunsPublisher,
maintenance_runs_publisher: MaintenanceRunsPublisher,
) -> None:
self._run_id = run_id
self._task_runner = task_runner
self._run_orchestrator_store = run_orchestrator_store
self._run_store = run_store
self._runs_publisher = runs_publisher
self._maintenance_runs_publisher = maintenance_runs_publisher

def create_action(
self,
Expand Down Expand Up @@ -80,6 +82,8 @@ def create_action(
func=self._run_protocol_and_insert_result,
deck_configuration=action_payload,
)
# Playing a protocol run terminates an existing maintenance run.
self._maintenance_runs_publisher.publish_current_maintenance_run()

elif action_type == RunActionType.PAUSE:
log.info(f'Pausing run "{self._run_id}".')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,20 @@ def __init__(self, client: NotificationClient) -> None:
"""Returns a configured Maintenance Runs Publisher."""
self._client = client

async def publish_current_maintenance_run(
async def publish_current_maintenance_run_async(
self,
) -> None:
"""Publishes the equivalent of GET /maintenance_run/current_run"""
await self._client.publish_advise_refetch_async(
topic=topics.MAINTENANCE_RUNS_CURRENT_RUN
)

def publish_current_maintenance_run(
self,
) -> None:
"""Publishes the equivalent of GET /maintenance_run/current_run"""
self._client.publish_advise_refetch(topic=topics.MAINTENANCE_RUNS_CURRENT_RUN)


_maintenance_runs_publisher_accessor: AppStateAccessor[
MaintenanceRunsPublisher
Expand Down
17 changes: 16 additions & 1 deletion robot-server/tests/runs/test_run_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from opentrons.protocol_engine.types import RunTimeParameter, BooleanParameter
from opentrons.protocol_runner import RunResult

from robot_server.service.notifications import RunsPublisher
from robot_server.service.notifications import RunsPublisher, MaintenanceRunsPublisher
from robot_server.service.task_runner import TaskRunner
from robot_server.runs.action_models import RunAction, RunActionType
from robot_server.runs.run_orchestrator_store import RunOrchestratorStore
Expand Down Expand Up @@ -48,6 +48,12 @@ def mock_runs_publisher(decoy: Decoy) -> RunsPublisher:
return decoy.mock(cls=RunsPublisher)


@pytest.fixture()
def mock_maintenance_runs_publisher(decoy: Decoy) -> MaintenanceRunsPublisher:
"""Get a mock RunsPublisher."""
return decoy.mock(cls=MaintenanceRunsPublisher)


@pytest.fixture
def run_id() -> str:
"""A run identifier value."""
Expand Down Expand Up @@ -99,6 +105,7 @@ def subject(
mock_run_store: RunStore,
mock_task_runner: TaskRunner,
mock_runs_publisher: RunsPublisher,
mock_maintenance_runs_publisher: MaintenanceRunsPublisher,
) -> RunController:
"""Get a RunController test subject."""
return RunController(
Expand All @@ -107,6 +114,7 @@ def subject(
run_store=mock_run_store,
task_runner=mock_task_runner,
runs_publisher=mock_runs_publisher,
maintenance_runs_publisher=mock_maintenance_runs_publisher,
)


Expand Down Expand Up @@ -144,6 +152,7 @@ async def test_create_play_action_to_start(
mock_run_store: RunStore,
mock_task_runner: TaskRunner,
mock_runs_publisher: RunsPublisher,
mock_maintenance_runs_publisher: MaintenanceRunsPublisher,
engine_state_summary: StateSummary,
run_time_parameters: List[RunTimeParameter],
protocol_commands: List[pe_commands.Command],
Expand Down Expand Up @@ -194,6 +203,12 @@ async def test_create_play_action_to_start(
times=1,
)

# Verify maintenance run publication after background task execution
decoy.verify(
mock_maintenance_runs_publisher.publish_current_maintenance_run(),
times=1,
)


def test_create_pause_action(
decoy: Decoy,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ async def test_publish_current_maintenance_run(
notification_client: AsyncMock, maintenance_runs_publisher: MaintenanceRunsPublisher
) -> None:
"""It should publish a notify flag for maintenance runs."""
await maintenance_runs_publisher.publish_current_maintenance_run()
await maintenance_runs_publisher.publish_current_maintenance_run_async()
notification_client.publish_advise_refetch_async.assert_awaited_once_with(
topic=topics.MAINTENANCE_RUNS_CURRENT_RUN
)

0 comments on commit 1b0afad

Please sign in to comment.