From eab0c8a408d2abe440e4af48c5c42ec9873ef93f Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Wed, 9 Apr 2025 14:34:27 +0200 Subject: [PATCH] Add dataset collection specific update endpoint --- client/src/api/schema/schema.ts | 80 +++++++++++++++++++ lib/galaxy/managers/hdcas.py | 1 - .../webapps/galaxy/api/history_contents.py | 22 +++++ lib/galaxy_test/api/test_history_contents.py | 32 ++++++++ lib/galaxy_test/base/populators.py | 26 ++++++ 5 files changed, 160 insertions(+), 1 deletion(-) diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 402ae925019..854c1f05005 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -219,6 +219,26 @@ export interface paths { patch?: never; trace?: never; }; + "/api/dataset_collections/{hdca_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** + * Updates the values for the history dataset (HDA) item with the given ``ID``. + * @description Updates the values for the history content item with the given ``ID``. + */ + put: operations["dataset_collections__update_collection"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/dataset_collections/{hdca_id}/contents/{parent_id}": { parameters: { query?: never; @@ -19351,6 +19371,66 @@ export interface operations { }; }; }; + dataset_collections__update_collection: { + parameters: { + query?: { + /** @description View to be passed to the serializer */ + view?: string | null; + /** @description Comma-separated list of keys to be passed to the serializer */ + keys?: string | null; + }; + header?: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + "run-as"?: string | null; + }; + path: { + /** @description The ID of the item (`HDA`/`HDCA`) */ + hdca_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateHistoryContentsPayload"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": + | components["schemas"]["HDACustom"] + | components["schemas"]["HDADetailed"] + | components["schemas"]["HDASummary"] + | components["schemas"]["HDAInaccessible"] + | components["schemas"]["HDCACustom"] + | components["schemas"]["HDCADetailed"] + | components["schemas"]["HDCASummary"]; + }; + }; + /** @description Request Error */ + "4XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageExceptionModel"]; + }; + }; + /** @description Server Error */ + "5XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageExceptionModel"]; + }; + }; + }; + }; contents_dataset_collection_api_dataset_collections__hdca_id__contents__parent_id__get: { parameters: { query?: { diff --git a/lib/galaxy/managers/hdcas.py b/lib/galaxy/managers/hdcas.py index b814890b6eb..e54d461b1aa 100644 --- a/lib/galaxy/managers/hdcas.py +++ b/lib/galaxy/managers/hdcas.py @@ -115,7 +115,6 @@ class HDCAManager( """ Use history to see if current user owns HDA. """ - super().is_owner(item, user, **kwargs) history = item.history assert history # allow anonymous user to access current history diff --git a/lib/galaxy/webapps/galaxy/api/history_contents.py b/lib/galaxy/webapps/galaxy/api/history_contents.py index 2386807211b..609ff23eaca 100644 --- a/lib/galaxy/webapps/galaxy/api/history_contents.py +++ b/lib/galaxy/webapps/galaxy/api/history_contents.py @@ -840,6 +840,28 @@ class FastAPIHistoryContents: contents_type=HistoryContentType.dataset, ) + @router.put( + "/api/dataset_collections/{hdca_id}", + summary="Updates the values for the history dataset (HDA) item with the given ``ID``.", + operation_id="dataset_collections__update_collection", + ) + def update_collection( + self, + hdca_id: HistoryItemIDPathParam, + trans: ProvidesHistoryContext = DependsOnTrans, + serialization_params: SerializationParams = Depends(query_serialization_params), + payload: UpdateHistoryContentsPayload = Body(...), + ) -> AnyHistoryContentItem: + """Updates the values for the history content item with the given ``ID``.""" + return self.service.update( + trans, + None, + hdca_id, + payload.model_dump(exclude_unset=True), + serialization_params, + contents_type=HistoryContentType.dataset_collection, + ) + @router.put( "/api/histories/{history_id}/contents/{id}", summary="Updates the values for the history content item with the given ``ID`` and query specified type. ``/api/histories/{history_id}/contents/{type}s/{id}`` should be used instead.", diff --git a/lib/galaxy_test/api/test_history_contents.py b/lib/galaxy_test/api/test_history_contents.py index f768931727a..07e8a999711 100644 --- a/lib/galaxy_test/api/test_history_contents.py +++ b/lib/galaxy_test/api/test_history_contents.py @@ -4,6 +4,7 @@ from typing import ( List, Optional, Tuple, + Union, ) from galaxy_test.api._framework import ApiTestCase @@ -426,6 +427,37 @@ class TestHistoryContentsApi(ApiTestCase): # and this is an invalid ID. assert update_response.status_code == 400, update_response.content + def test_rename_dataset(self, history_id): + dataset = self.dataset_populator.new_dataset(history_id, wait=True) + self.dataset_populator.rename_dataset(dataset["id"]) + hda = self.dataset_populator.get_history_dataset_details(history_id=history_id, content_id=dataset["id"]) + assert hda["name"] != dataset["name"] + with self._different_user(): + exception: Union[Exception, None] = None + try: + self.dataset_populator.rename_dataset(dataset["id"]) + except AssertionError as e: + exception = e + assert "HistoryDatasetAssociation is not owned by user" in str(exception) + + def test_rename_dataset_collection(self, history_id): + dataset_collection = self.dataset_collection_populator.create_list_in_history( + history_id, contents=["a\nb\nc\nd", "e\nf\ng\nh"], wait=True + ).json()["output_collections"][0] + dataset_collection_id = dataset_collection["id"] + self.dataset_populator.rename_collection(dataset_collection_id) + hdca = self.dataset_populator.get_history_collection_details( + history_id=history_id, content_id=dataset_collection_id + ) + assert hdca["name"] != dataset_collection["name"] + with self._different_user(): + exception: Union[Exception, None] = None + try: + self.dataset_populator.rename_collection(dataset_collection_id) + except AssertionError as e: + exception = e + assert "HistoryDatasetCollectionAssociation is not owned by user" in str(exception) + def test_update_batch(self, history_id): hda1 = self._wait_for_new_hda(history_id) assert str(hda1["deleted"]).lower() == "false" diff --git a/lib/galaxy_test/base/populators.py b/lib/galaxy_test/base/populators.py index 74817eab4d0..a87eba9b73f 100644 --- a/lib/galaxy_test/base/populators.py +++ b/lib/galaxy_test/base/populators.py @@ -832,6 +832,20 @@ class BaseDatasetPopulator(BasePopulator): wait_on(_wait_for_purge, "dataset to become purged", timeout=2) return self._get(dataset_url) + def rename_dataset( + self, + content_id: str, + new_name: Optional[str] = None, + ): + if not new_name: + new_name = self.get_random_name() + return self.update_dataset(content_id, {"name": new_name}) + + def rename_collection(self, content_id: str, new_name: Optional[str] = None): + if not new_name: + new_name = self.get_random_name() + self.update_dataset_collection(content_id, {"name": new_name}) + def create_tool_landing(self, payload: CreateToolLandingRequestPayload) -> ToolLandingRequest: create_url = "tool_landings" json = payload.model_dump(mode="json") @@ -1533,6 +1547,18 @@ class BaseDatasetPopulator(BasePopulator): put_response = self._put(update_url, payload, json=True) return put_response + def update_dataset(self, dataset_id: str, update_payload: Dict[str, Any]): + update_url = f"datasets/{dataset_id}" + put_response = self._put(update_url, update_payload, json=True) + api_asserts.assert_status_code_is_ok(put_response) + return put_response.json() + + def update_dataset_collection(self, dataset_collection_id: str, update_payload: Dict[str, Any]): + update_url = f"dataset_collections/{dataset_collection_id}" + put_response = self._put(update_url, update_payload, json=True) + api_asserts.assert_status_code_is_ok(put_response) + return put_response.json() + def get_histories(self): history_index_response = self._get("histories") api_asserts.assert_status_code_is(history_index_response, 200)