diff --git a/client/src/components/History/Content/Collection/CollectionDescription.test.js b/client/src/components/History/Content/Collection/CollectionDescription.test.js index 50c9f7729b8..9a485b7ea1e 100644 --- a/client/src/components/History/Content/Collection/CollectionDescription.test.js +++ b/client/src/components/History/Content/Collection/CollectionDescription.test.js @@ -11,23 +11,50 @@ describe("CollectionDescription", () => { wrapper = mount(CollectionDescription, { propsData: { collectionType: "list", - elementCount: 10, }, localVue, }); }); - it("check basics", async () => { - const details = wrapper.findAll("span"); - expect(details.at(0).text()).toBe("a list"); - expect(details.at(1).text()).toBe("with 10 items"); - await wrapper.setProps({ elementCount: 1 }); - expect(details.at(1).text()).toBe("with 1 item"); - await wrapper.setProps({ collectionType: "paired" }); - expect(details.at(0).text()).toBe("a dataset pair"); + it("should display expected heterogeneous descriptions", async () => { + const HETEROGENEOUS_DATATYPES = ["txt", "csv", "tabular"]; + await wrapper.setProps({ elementCount: 1, elementsDatatypes: HETEROGENEOUS_DATATYPES }); + expect(wrapper.text()).toBe("a list with 1 dataset"); + + await wrapper.setProps({ elementCount: 2, collectionType: "paired" }); + expect(wrapper.text()).toBe("a pair with 2 datasets"); + + await wrapper.setProps({ elementCount: 10, collectionType: "list" }); + expect(wrapper.text()).toBe("a list with 10 datasets"); + await wrapper.setProps({ collectionType: "list:paired" }); - expect(details.at(0).text()).toBe("a list of pairs"); + expect(wrapper.text()).toBe("a list with 10 pairs"); + + await wrapper.setProps({ collectionType: "list:list" }); + expect(wrapper.text()).toBe("a list with 10 lists"); + await wrapper.setProps({ collectionType: "other" }); - expect(details.at(0).text()).toBe("a nested list"); + expect(wrapper.text()).toBe("a nested list with 10 dataset collections"); + }); + + it("should display expected homogeneous descriptions", async () => { + const EXPECTED_HOMOGENEOUS_DATATYPE = "tabular"; + await wrapper.setProps({ elementCount: 1, elementsDatatypes: [EXPECTED_HOMOGENEOUS_DATATYPE] }); + expect(wrapper.text()).toBe(`a list with 1 ${EXPECTED_HOMOGENEOUS_DATATYPE} dataset`); + + await wrapper.setProps({ elementCount: 2, collectionType: "paired" }); + expect(wrapper.text()).toBe(`a pair with 2 ${EXPECTED_HOMOGENEOUS_DATATYPE} datasets`); + + await wrapper.setProps({ elementCount: 10, collectionType: "list" }); + expect(wrapper.text()).toBe(`a list with 10 ${EXPECTED_HOMOGENEOUS_DATATYPE} datasets`); + + await wrapper.setProps({ collectionType: "list:paired" }); + expect(wrapper.text()).toBe(`a list with 10 ${EXPECTED_HOMOGENEOUS_DATATYPE} pairs`); + + await wrapper.setProps({ collectionType: "list:list" }); + expect(wrapper.text()).toBe(`a list with 10 ${EXPECTED_HOMOGENEOUS_DATATYPE} lists`); + + await wrapper.setProps({ collectionType: "other" }); + expect(wrapper.text()).toBe(`a nested list with 10 ${EXPECTED_HOMOGENEOUS_DATATYPE} dataset collections`); }); }); diff --git a/client/src/components/History/Content/Collection/CollectionDescription.vue b/client/src/components/History/Content/Collection/CollectionDescription.vue index b1cfcc3e732..faa2fd5947a 100644 --- a/client/src/components/History/Content/Collection/CollectionDescription.vue +++ b/client/src/components/History/Content/Collection/CollectionDescription.vue @@ -1,8 +1,6 @@ @@ -10,21 +8,55 @@ export default { props: { collectionType: { type: String, required: true }, - elementCount: { type: Number, required: true }, + elementCount: { type: Number, required: false, default: undefined }, + elementsDatatypes: { type: Array, required: false, default: () => [] }, }, data() { return { labels: { list: "list", - paired: "dataset pair", - "list:paired": "list of pairs", + "list:paired": "list", + "list:list": "list", + paired: "pair", }, }; }, computed: { + /**@return {String} */ collectionLabel() { return this.labels[this.collectionType] || "nested list"; }, + /**@return {Boolean} */ + hasSingleElement() { + return this.elementCount === 1; + }, + /**@return {Boolean} */ + isHomogeneous() { + return this.elementsDatatypes.length === 1; + }, + /**@return {String} */ + homogeneousDatatype() { + return this.isHomogeneous ? ` ${this.elementsDatatypes[0]}` : ""; + }, + /**@return {String} */ + pluralizedItem() { + if (this.collectionType === "list:list") { + return this.pluralize("list"); + } + if (this.collectionType === "list:paired") { + return this.pluralize("pair"); + } + if (!Object.keys(this.labels).includes(this.collectionType)) { + //Any other kind of nested collection + return this.pluralize("dataset collection"); + } + return this.pluralize("dataset"); + }, + }, + methods: { + pluralize(word) { + return this.hasSingleElement ? word : `${word}s`; + }, }, }; diff --git a/client/src/components/History/Content/ContentItem.vue b/client/src/components/History/Content/ContentItem.vue index 52ef4928ffe..640637a45aa 100644 --- a/client/src/components/History/Content/ContentItem.vue +++ b/client/src/components/History/Content/ContentItem.vue @@ -32,7 +32,8 @@ + :element-count="item.element_count" + :elements-datatypes="item.elements_datatypes" />
diff --git a/client/src/components/History/CurrentCollection/CollectionDetails.vue b/client/src/components/History/CurrentCollection/CollectionDetails.vue index a8cf7df5363..f09ec9d7d3c 100644 --- a/client/src/components/History/CurrentCollection/CollectionDetails.vue +++ b/client/src/components/History/CurrentCollection/CollectionDetails.vue @@ -7,7 +7,10 @@ @save="$emit('update:dsc', $event)"> diff --git a/client/src/components/providers/test/json/DatasetCollection.heterogeneous.json b/client/src/components/providers/test/json/DatasetCollection.heterogeneous.json deleted file mode 100644 index 0aad7ebd8af..00000000000 --- a/client/src/components/providers/test/json/DatasetCollection.heterogeneous.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "job_source_id": null, - "populated_state": "ok", - "type": "collection", - "hid": 9, - "name": "Heterogeneous list", - "url": "/api/histories/4ff6f47412c3e65e/contents/dataset_collections/5a1cff6882ddb5b2", - "element_count": 4, - "job_source_type": null, - "history_id": "4ff6f47412c3e65e", - "id": "5a1cff6882ddb5b2", - "type_id": "dataset_collection-5a1cff6882ddb5b2", - "create_time": "2020-06-26T14:22:58.435340", - "collection_type": "list", - "history_content_type": "dataset_collection", - "populated_state_message": null, - "job_state_summary": { - "error": 0, - "upload": 0, - "queued": 0, - "all_jobs": 0, - "paused": 0, - "ok": 0, - "new": 0, - "waiting": 0, - "failed": 0, - "deleted": 0, - "resubmitted": 0, - "running": 0, - "deleted_new": 0 - }, - "contents_url": "/api/dataset_collections/5a1cff6882ddb5b2/contents/f09437b8822035f7", - "tags": [], - "visible": true, - "deleted": false, - "populated": true, - "update_time": "2020-06-26T14:22:58.435348", - "collection_id": 23, - "elements_datatypes": ["txt", "csv", "tabular"] -} diff --git a/client/src/components/providers/test/json/DatasetCollection.homogeneous.json b/client/src/components/providers/test/json/DatasetCollection.homogeneous.json deleted file mode 100644 index fcb738124be..00000000000 --- a/client/src/components/providers/test/json/DatasetCollection.homogeneous.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "job_source_id": null, - "populated_state": "ok", - "type": "collection", - "hid": 9, - "name": "Homogeneous list", - "url": "/api/histories/4ff6f47412c3e65e/contents/dataset_collections/5a1cff6882ddb5b2", - "element_count": 4, - "job_source_type": null, - "history_id": "4ff6f47412c3e65e", - "id": "5a1cff6882ddb5b2", - "type_id": "dataset_collection-5a1cff6882ddb5b2", - "create_time": "2020-06-26T14:22:58.435340", - "collection_type": "list", - "history_content_type": "dataset_collection", - "populated_state_message": null, - "job_state_summary": { - "error": 0, - "upload": 0, - "queued": 0, - "all_jobs": 0, - "paused": 0, - "ok": 0, - "new": 0, - "waiting": 0, - "failed": 0, - "deleted": 0, - "resubmitted": 0, - "running": 0, - "deleted_new": 0 - }, - "contents_url": "/api/dataset_collections/5a1cff6882ddb5b2/contents/f09437b8822035f7", - "tags": [], - "visible": true, - "deleted": false, - "populated": true, - "update_time": "2020-06-26T14:22:58.435348", - "collection_id": 23, - "elements_datatypes": ["txt"] -} diff --git a/lib/galaxy/webapps/galaxy/services/history_contents.py b/lib/galaxy/webapps/galaxy/services/history_contents.py index b3b7c4ad7bf..fa528db14c8 100644 --- a/lib/galaxy/webapps/galaxy/services/history_contents.py +++ b/lib/galaxy/webapps/galaxy/services/history_contents.py @@ -971,6 +971,7 @@ class HistoriesContentsService(ServiceBase): f"Invalid filter found. When requesting stats, please avoid filtering by {non_orm_filter_keys}" ) + serialization_params = self._handle_extra_serialization_for_media_type(serialization_params, accept) filter_query_params.order = filter_query_params.order or "hid-asc" order_by = self.build_order_by(self.history_contents_manager, filter_query_params.order) contents = self.history_contents_manager.contents( @@ -999,6 +1000,18 @@ class HistoriesContentsService(ServiceBase): return HistoryContentsWithStatsResult.construct(contents=items, stats=stats) return HistoryContentsResult.construct(__root__=items) + def _handle_extra_serialization_for_media_type( + self, + serialization_params: SerializationParams, + request_media_type: str, + ) -> SerializationParams: + """According to the requested media type the response may include extra information.""" + if request_media_type == HistoryContentsWithStatsResult.__accept_type__: + if not serialization_params.keys: + serialization_params.keys = [] + serialization_params.keys.append("elements_datatypes") + return serialization_params + def _serialize_legacy_content_item( self, trans, diff --git a/lib/galaxy_test/api/test_history_contents.py b/lib/galaxy_test/api/test_history_contents.py index 511aa61acfa..848b046a061 100644 --- a/lib/galaxy_test/api/test_history_contents.py +++ b/lib/galaxy_test/api/test_history_contents.py @@ -1138,6 +1138,20 @@ class HistoryContentsApiBulkOperationTestCase(ApiTestCase): ) self._assert_status_code_is(response, 400) + def test_index_with_stats_has_extra_serialization(self): + expected_extra_keys_in_collections = ["elements_datatypes"] + with self.dataset_populator.test_history() as history_id: + self._create_collection_in_history(history_id) + response = self._get_contents_with_stats( + history_id, + search_query="&q=history_content_type-eq&qv=dataset_collection", + ) + self._assert_status_code_is(response, 200) + contents_with_stats = response.json() + assert contents_with_stats["contents"] + collection = contents_with_stats["contents"][0] + self._assert_has_keys(collection, *expected_extra_keys_in_collections) + def _get_contents_with_stats(self, history_id: str, search_query: str = ""): headers = {"accept": "application/vnd.galaxy.history.contents.stats+json"} search_response = self._get(f"histories/{history_id}/contents?v=dev{search_query}", headers=headers)