diff --git a/robot-server/robot_server/protocols/protocol_store.py b/robot-server/robot_server/protocols/protocol_store.py index d2d3574856a..a080276594b 100644 --- a/robot-server/robot_server/protocols/protocol_store.py +++ b/robot-server/robot_server/protocols/protocol_store.py @@ -188,7 +188,10 @@ def get(self, protocol_id: str) -> ProtocolResource: @lru_cache(maxsize=_CACHE_ENTRIES) def get_all(self) -> List[ProtocolResource]: - """Get all protocols currently saved in this store.""" + """Get all protocols currently saved in this store. + + Results are ordered from first-added to last-added. + """ all_sql_resources = self._sql_get_all() return [ ProtocolResource( @@ -297,17 +300,16 @@ def get_referencing_run_ids(self, protocol_id: str) -> List[str]: See the `runs` module for information about runs. - Results are ordered with the oldest-added (NOT created) run first. + Results are ordered with the oldest run first. """ - select_referencing_run_ids = sqlalchemy.select(run_table.c.id).where( - run_table.c.protocol_id == protocol_id + select_referencing_run_ids = ( + sqlalchemy.select(run_table.c.id) + .where(run_table.c.protocol_id == protocol_id) + .order_by(sqlite_rowid) ) with self._sql_engine.begin() as transaction: - referencing_run_ids = ( - transaction.execute(select_referencing_run_ids).scalars().all() - ) - return referencing_run_ids + return transaction.execute(select_referencing_run_ids).scalars().all() def _sql_insert(self, resource: _DBProtocolResource) -> None: statement = sqlalchemy.insert(protocol_table).values( @@ -334,7 +336,7 @@ def _sql_get_all(self) -> List[_DBProtocolResource]: def _sql_get_all_from_engine( sql_engine: sqlalchemy.engine.Engine, ) -> List[_DBProtocolResource]: - statement = sqlalchemy.select(protocol_table) + statement = sqlalchemy.select(protocol_table).order_by(sqlite_rowid) with sql_engine.begin() as transaction: all_rows = transaction.execute(statement).all() return [_convert_sql_row_to_dataclass(sql_row=row) for row in all_rows] diff --git a/robot-server/robot_server/protocols/router.py b/robot-server/robot_server/protocols/router.py index 09eaedea1f9..1b046fcc2b3 100644 --- a/robot-server/robot_server/protocols/router.py +++ b/robot-server/robot_server/protocols/router.py @@ -108,7 +108,10 @@ class ProtocolLinks(BaseModel): referencingRuns: List[RunLink] = Field( ..., - description="Links to runs that reference the protocol.", + description=( + "Links to runs that reference the protocol," + " in order from the oldest run to the newest run." + ), ) @@ -129,11 +132,18 @@ class ProtocolLinks(BaseModel): When too many protocols already exist, old ones will be automatically deleted to make room for the new one. A protocol will never be automatically deleted if there's a run - referring to it, though. + referring to it, though. (See the `/runs/` endpoints.) + + If you upload the exact same set of files multiple times, the first protocol + resource will be returned instead of creating duplicate ones. + + When a new protocol resource is created, an analysis is started for it. + See the `/protocols/{id}/analyses/` endpoints. """ ), status_code=status.HTTP_201_CREATED, responses={ + status.HTTP_200_OK: {"model": SimpleBody[Protocol]}, status.HTTP_201_CREATED: {"model": SimpleBody[Protocol]}, status.HTTP_422_UNPROCESSABLE_ENTITY: { "model": ErrorBody[Union[ProtocolFilesInvalid, ProtocolRobotTypeMismatch]] @@ -280,6 +290,7 @@ async def create_protocol( protocols_router.get, path="/protocols", summary="Get uploaded protocols", + description="Return all stored protocols, in order from first-uploaded to last-uploaded.", responses={status.HTTP_200_OK: {"model": SimpleMultiBody[Protocol]}}, ) async def get_protocols( diff --git a/robot-server/robot_server/runs/router/base_router.py b/robot-server/robot_server/runs/router/base_router.py index 3edd9a342ba..b44dba2a17a 100644 --- a/robot-server/robot_server/runs/router/base_router.py +++ b/robot-server/robot_server/runs/router/base_router.py @@ -202,7 +202,9 @@ async def create_run( base_router.get, path="/runs", summary="Get all runs", - description="Get a list of all active and inactive runs.", + description=( + "Get a list of all active and inactive runs, in order from oldest to newest." + ), responses={ status.HTTP_200_OK: {"model": MultiBody[Run, AllRunsLinks]}, }, diff --git a/robot-server/robot_server/runs/router/commands_router.py b/robot-server/robot_server/runs/router/commands_router.py index a8767ca5482..093c6ec925b 100644 --- a/robot-server/robot_server/runs/router/commands_router.py +++ b/robot-server/robot_server/runs/router/commands_router.py @@ -235,6 +235,9 @@ async def create_run_command( summary="Get a list of all protocol commands in the run", description=( "Get a list of all commands in the run and their statuses. " + "\n\n" + "The commands are returned in order from oldest to newest." + "\n\n" "This endpoint returns command summaries. Use " "`GET /runs/{runId}/commands/{commandId}` to get all " "information available for a given command." diff --git a/robot-server/robot_server/runs/run_data_manager.py b/robot-server/robot_server/runs/run_data_manager.py index 74f1d8a4db9..0fc6ee2b731 100644 --- a/robot-server/robot_server/runs/run_data_manager.py +++ b/robot-server/robot_server/runs/run_data_manager.py @@ -195,8 +195,10 @@ def get_run_loaded_labware_definitions( def get_all(self, length: Optional[int]) -> List[Run]: """Get current and stored run resources. - Returns: - All run resources. + Results are ordered from oldest to newest. + + Params: + length: If `None`, return all runs. Otherwise, return the newest n runs. """ return [ _build_run( diff --git a/robot-server/robot_server/runs/run_models.py b/robot-server/robot_server/runs/run_models.py index 7f435e054f0..ee85902440a 100644 --- a/robot-server/robot_server/runs/run_models.py +++ b/robot-server/robot_server/runs/run_models.py @@ -73,7 +73,7 @@ class Run(ResourceModel): ) actions: List[RunAction] = Field( ..., - description="Client-initiated run control actions.", + description="Client-initiated run control actions, ordered oldest to newest.", ) errors: List[ErrorOccurrence] = Field( ..., diff --git a/robot-server/robot_server/runs/run_store.py b/robot-server/robot_server/runs/run_store.py index 319e9340943..849b82dafea 100644 --- a/robot-server/robot_server/runs/run_store.py +++ b/robot-server/robot_server/runs/run_store.py @@ -104,8 +104,10 @@ def update_run_state( select_run_resource = sqlalchemy.select(*_run_columns).where( run_table.c.id == run_id ) - select_actions = sqlalchemy.select(action_table).where( - action_table.c.run_id == run_id + select_actions = ( + sqlalchemy.select(action_table) + .where(action_table.c.run_id == run_id) + .order_by(sqlite_rowid) ) with self._sql_engine.begin() as transaction: @@ -220,8 +222,10 @@ def get(self, run_id: str) -> RunResource: run_table.c.id == run_id ) - select_actions = sqlalchemy.select(action_table).where( - action_table.c.run_id == run_id + select_actions = ( + sqlalchemy.select(action_table) + .where(action_table.c.run_id == run_id) + .order_by(sqlite_rowid) ) with self._sql_engine.begin() as transaction: @@ -237,26 +241,28 @@ def get(self, run_id: str) -> RunResource: def get_all(self, length: Optional[int] = None) -> List[RunResource]: """Get all known run resources. - Returns: - All stored run entries. + Results are ordered from oldest to newest. + + Params: + length: If `None`, return all runs. Otherwise, return the newest n runs. """ - select_runs = sqlalchemy.select(*_run_columns) select_actions = sqlalchemy.select(action_table).order_by(sqlite_rowid.asc()) actions_by_run_id = defaultdict(list) with self._sql_engine.begin() as transaction: if length is not None: select_runs = ( - select_runs.limit(length) + sqlalchemy.select(*_run_columns) .order_by(sqlite_rowid.desc()) .limit(length) ) # need to select the last inserted runs and return by asc order runs = list(reversed(transaction.execute(select_runs).all())) else: - runs = transaction.execute( - select_runs.order_by(sqlite_rowid.asc()) - ).all() + select_runs = sqlalchemy.select(*_run_columns).order_by( + sqlite_rowid.asc() + ) + runs = transaction.execute(select_runs).all() actions = transaction.execute(select_actions).all()