diff --git a/client/galaxy/scripts/mvc/library/library-dataset-view.js b/client/galaxy/scripts/mvc/library/library-dataset-view.js index 53c6d30eb6f..8137376b77f 100644 --- a/client/galaxy/scripts/mvc/library/library-dataset-view.js +++ b/client/galaxy/scripts/mvc/library/library-dataset-view.js @@ -703,6 +703,12 @@ var LibraryDatasetView = Backbone.View.extend({ <%= _.escape(item.get("file_size")) %> <% } %> + <% if (item.get("update_time")) { %> + + Date last updated (UTC) + <%= _.escape(item.get("update_time")) %> + + <% } %> <% if (item.get("date_uploaded")) { %> Date uploaded (UTC) @@ -880,6 +886,12 @@ var LibraryDatasetView = Backbone.View.extend({ <%= _.escape(ldda.get("file_size")) %> <% } %> + <% if (ldda.get("update_time")) { %> + + Date last updated (UTC) + <%= _.escape(ldda.get("update_time")) %> + + <% } %> <% if (ldda.get("date_uploaded")) { %> Date uploaded (UTC) @@ -1006,6 +1018,10 @@ var LibraryDatasetView = Backbone.View.extend({ Size <%= _.escape(item.get("file_size")) %> + + Date last updated (UTC) + <%= _.escape(item.get("update_time")) %> + Date uploaded (UTC) <%= _.escape(item.get("date_uploaded")) %> diff --git a/client/galaxy/scripts/mvc/library/library-folderlist-view.js b/client/galaxy/scripts/mvc/library/library-folderlist-view.js index 1b6c271f99c..cedc161f341 100644 --- a/client/galaxy/scripts/mvc/library/library-folderlist-view.js +++ b/client/galaxy/scripts/mvc/library/library-folderlist-view.js @@ -517,7 +517,7 @@ var FolderListView = Backbone.View.extend({ - Time Updated (UTC) + Date Updated (UTC) diff --git a/client/galaxy/scripts/mvc/library/library-util.js b/client/galaxy/scripts/mvc/library/library-util.js index c4d546c4b25..bb174a7cb56 100644 --- a/client/galaxy/scripts/mvc/library/library-util.js +++ b/client/galaxy/scripts/mvc/library/library-util.js @@ -1,45 +1,38 @@ /** - * Create alphabetical based two-argument comparator - * that takes into account that Folder comes before Dataset. + * Create alphabetical based two-argument comparator to handle library items (including folders) * If sort_key is not present it is set to ''. * @param {str} sort_key key to sort by * @param {str} sort_order order to sort by (asc, desc) * @return {function} two-argument comparator function */ var generateComparator = (sort_key, sort_order) => (itemA, itemB) => { - if (itemA.get("type") === itemB.get("type")) { - if (!itemA.has(sort_key) && !itemB.has(sort_key)) { - return 0; - } else if (!itemA.has(sort_key)) { - return 1; - } else if (!itemB.has(sort_key)) { - return -1; - } - var comparable_itemA_key; - var comparable_itemB_key; - if (typeof itemA.get(sort_key) === "number") { - comparable_itemA_key = itemA.get(sort_key); - comparable_itemB_key = itemB.get(sort_key); - } - else - { - comparable_itemA_key = itemA.get(sort_key).toLowerCase(); - comparable_itemB_key = itemB.get(sort_key).toLowerCase(); - } - if ( comparable_itemA_key > comparable_itemB_key) { - return sort_order === "asc" ? 1 : -1; - } - if (comparable_itemB_key > comparable_itemA_key) { - return sort_order === "asc" ? -1 : 1; - } - - return 0; // equal - } else { - if (itemA.get("type") === "folder") { - return -1; // folder is always before dataset - } + if (!itemA.has(sort_key) && !itemB.has(sort_key)) { + return 0; + } else if (!itemA.has(sort_key)) { return 1; + } else if (!itemB.has(sort_key)) { + return -1; } + var comparable_itemA_key; + var comparable_itemB_key; + if (typeof itemA.get(sort_key) === "number") { + comparable_itemA_key = itemA.get(sort_key); + comparable_itemB_key = itemB.get(sort_key); + } + else + { + comparable_itemA_key = itemA.get(sort_key).toLowerCase(); + comparable_itemB_key = itemB.get(sort_key).toLowerCase(); + } + + if ( comparable_itemA_key > comparable_itemB_key) { + return sort_order === "asc" ? 1 : -1; + } + if (comparable_itemB_key > comparable_itemA_key) { + return sort_order === "asc" ? -1 : 1; + } + + return 0; // equal }; export default { generateComparator: generateComparator diff --git a/lib/galaxy/managers/library_datasets.py b/lib/galaxy/managers/library_datasets.py index 6cdf791fd3a..20b76a1bf50 100644 --- a/lib/galaxy/managers/library_datasets.py +++ b/lib/galaxy/managers/library_datasets.py @@ -101,6 +101,7 @@ class LibraryDatasetsManager(datasets.DatasetAssociationManager): ldda.dbkey = new_genome_build changed = True if changed: + ldda.update_parent_folder_update_times() trans.sa_session.add(ldda) trans.sa_session.flush() return changed @@ -223,6 +224,7 @@ class LibraryDatasetsManager(datasets.DatasetAssociationManager): rval['full_path'] = full_path rval['file_size'] = util.nice_size(int(ldda.get_size())) rval['date_uploaded'] = ldda.create_time.strftime("%Y-%m-%d %I:%M %p") + rval['update_time'] = ldda.update_time.strftime("%Y-%m-%d %I:%M %p") rval['can_user_modify'] = trans.user_is_admin or trans.app.security_agent.can_modify_library_item(current_user_roles, ld) rval['is_unrestricted'] = trans.app.security_agent.dataset_is_public(ldda.dataset) rval['tags'] = self.tag_handler.get_tags_str(ldda.tags) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 448f42f442b..1e075149559 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -31,6 +31,7 @@ from sqlalchemy import ( not_, or_, select, + text, true, type_coerce, types) @@ -3425,6 +3426,7 @@ class LibraryDataset(RepresentById): uploaded_by=ldda.user.email, message=ldda.message, date_uploaded=ldda.create_time.isoformat(), + update_time=ldda.update_time.isoformat(), file_size=int(ldda.get_size()), file_ext=ldda.ext, data_type=ldda.datatype.__class__.__module__ + '.' + ldda.datatype.__class__.__name__, @@ -3593,6 +3595,32 @@ class LibraryDatasetDatasetAssociation(DatasetInstance, HasName, RepresentById): rval['metadata_' + name] = val return rval + def update_parent_folder_update_times(self): + # sets the update_time for all continaing folders up the tree + ldda = self + + sql = text( + ''' + WITH RECURSIVE parent_folders_of(folder_id) AS + (SELECT folder_id + FROM library_dataset + WHERE id = :library_dataset_id + UNION ALL + SELECT library_folder.parent_id + FROM library_folder, parent_folders_of + WHERE library_folder.id = parent_folders_of.folder_id ) + UPDATE library_folder + SET update_time = + (SELECT update_time + FROM library_dataset_dataset_association + WHERE id = :ldda_id) + WHERE exists (SELECT 1 FROM parent_folders_of + WHERE library_folder.id = parent_folders_of.folder_id) + ''').execution_options(autocommit=True) + ret = object_session(self).execute(sql, {'library_dataset_id': ldda.library_dataset_id, 'ldda_id': ldda.id}) + if ret.rowcount < 1: + log.warn('Attempt to updated parent folder times failed: {0} records updated.'.format(ret.rowcount)) + class ExtendedMetadata(RepresentById): def __init__(self, data): diff --git a/lib/galaxy/webapps/base/controller.py b/lib/galaxy/webapps/base/controller.py index 41c1d66722e..f08c3b81d0b 100644 --- a/lib/galaxy/webapps/base/controller.py +++ b/lib/galaxy/webapps/base/controller.py @@ -546,6 +546,7 @@ class UsesLibraryMixinItems(SharableItemSecurityMixin): # I don't see a reason why hdas copied into libraries should not be visible. # If there is, refactor `ldda.visible = True` to do this only when adding HDCAs. ldda.visible = True + ldda.update_parent_folder_update_times() trans.sa_session.flush() ldda_dict = ldda.to_dict() rval = trans.security.encode_dict_ids(ldda_dict) diff --git a/lib/galaxy/webapps/galaxy/api/folder_contents.py b/lib/galaxy/webapps/galaxy/api/folder_contents.py index a7ecc98481d..da81e0229d8 100644 --- a/lib/galaxy/webapps/galaxy/api/folder_contents.py +++ b/lib/galaxy/webapps/galaxy/api/folder_contents.py @@ -81,13 +81,13 @@ class FolderContentsController(BaseAPIController, UsesLibraryMixin, UsesLibraryM for content_item in self._load_folder_contents(trans, folder, deleted): return_item = {} encoded_id = trans.security.encode_id(content_item.id) - update_time = content_item.update_time.strftime("%Y-%m-%d %I:%M %p") create_time = content_item.create_time.strftime("%Y-%m-%d %I:%M %p") if content_item.api_type == 'folder': encoded_id = 'F' + encoded_id can_modify = is_admin or (trans.user and trans.app.security_agent.can_modify_library_item(current_user_roles, folder)) can_manage = is_admin or (trans.user and trans.app.security_agent.can_manage_library_item(current_user_roles, folder)) + update_time = content_item.update_time.strftime("%Y-%m-%d %I:%M %p") return_item.update(dict(can_modify=can_modify, can_manage=can_manage)) if content_item.description: return_item.update(dict(description=content_item.description)) @@ -107,11 +107,13 @@ class FolderContentsController(BaseAPIController, UsesLibraryMixin, UsesLibraryM can_manage = is_admin or (trans.user and trans.app.security_agent.can_manage_dataset(current_user_roles, content_item.library_dataset_dataset_association.dataset)) raw_size = int(content_item.library_dataset_dataset_association.get_size()) nice_size = util.nice_size(raw_size) + update_time = content_item.library_dataset_dataset_association.update_time.strftime("%Y-%m-%d %I:%M %p") library_dataset_dict = content_item.to_dict() encoded_ldda_id = trans.security.encode_id(content_item.library_dataset_dataset_association.id) return_item.update(dict(file_ext=library_dataset_dict['file_ext'], date_uploaded=library_dataset_dict['date_uploaded'], + update_time=update_time, is_unrestricted=is_unrestricted, is_private=is_private, can_manage=can_manage, diff --git a/test/api/test_libraries.py b/test/api/test_libraries.py index a9f8d5f4f05..11454c23695 100644 --- a/test/api/test_libraries.py +++ b/test/api/test_libraries.py @@ -232,6 +232,26 @@ class LibrariesApiTestCase(api.ApiTestCase, TestsDatasets): self._assert_status_code_is(create_response, 200) self._assert_has_keys(create_response.json(), "name", "id") + def test_create_dataset_in_subfolder(self): + library = self.library_populator.new_private_library("ForCreateDatasets") + folder_response = self._create_folder(library) + self._assert_status_code_is(folder_response, 200) + folder_id = folder_response.json()[0]['id'] + subfolder_response = self._create_subfolder(folder_id) + self._assert_status_code_is(folder_response, 200) + print(subfolder_response.json()) + subfolder_id = subfolder_response.json()['id'] + history_id = self.dataset_populator.new_history() + hda_id = self.dataset_populator.new_dataset(history_id, content="1 2 3 sub")['id'] + payload = {'from_hda_id': hda_id} + create_response = self._post("folders/%s/contents" % subfolder_id, payload) + self._assert_status_code_is(create_response, 200) + self._assert_has_keys(create_response.json(), "name", "id") + dataset_update_time = create_response.json()['update_time'] + container_fetch_response = self.galaxy_interactor.get("folders/%s/contents" % folder_id) + container_update_time = container_fetch_response.json()['folder_contents'][0]['update_time'] + assert dataset_update_time == container_update_time, container_fetch_response + def test_update_dataset_in_folder(self): ld = self._create_dataset_in_folder_in_library("ForUpdateDataset") data = {'name': 'updated_name', 'file_ext': 'fastq', 'misc_info': 'updated_info', 'genome_build': 'updated_genome_build'} @@ -301,6 +321,13 @@ class LibrariesApiTestCase(api.ApiTestCase, TestsDatasets): ) return self._post("libraries/%s/contents" % library["id"], data=create_data) + def _create_subfolder(self, containing_folder_id): + create_data = dict( + description="new subfolder desc", + name="New Subfolder", + ) + return self._post("folders/%s" % containing_folder_id, data=create_data) + def _create_dataset_in_folder_in_library(self, library_name): library = self.library_populator.new_private_library(library_name) folder_response = self._create_folder(library)