diff --git a/config/tool_conf.xml.sample b/config/tool_conf.xml.sample index ae8bc9b96685..890a6f1a183e 100644 --- a/config/tool_conf.xml.sample +++ b/config/tool_conf.xml.sample @@ -39,6 +39,7 @@ +
diff --git a/lib/galaxy/managers/taggable.py b/lib/galaxy/managers/taggable.py index edee585460ed..dc4508abefac 100644 --- a/lib/galaxy/managers/taggable.py +++ b/lib/galaxy/managers/taggable.py @@ -24,7 +24,7 @@ def _tag_str_gen(item): def _tags_to_strings(item): if not hasattr(item, 'tags'): return None - return list(_tag_str_gen(item)) + return sorted(list(_tag_str_gen(item))) def _tags_from_strings(item, tag_handler, new_tags_list, user=None): diff --git a/lib/galaxy/managers/tags.py b/lib/galaxy/managers/tags.py index b3d3b0743f97..8e37c13ed57b 100644 --- a/lib/galaxy/managers/tags.py +++ b/lib/galaxy/managers/tags.py @@ -169,6 +169,8 @@ def apply_item_tag(self, user, item, name, value=None): item_tag_assoc.user_tname = name item_tag_assoc.user_value = value item_tag_assoc.value = lc_value + # Need to flush to get an ID. We need an ID to apply multiple tags with the same tname to an object. + self.sa_session.flush() return item_tag_assoc def apply_item_tags(self, user, item, tags_str): diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py index 928b2bfee69b..db471241b614 100755 --- a/lib/galaxy/tools/__init__.py +++ b/lib/galaxy/tools/__init__.py @@ -28,6 +28,7 @@ from galaxy.datatypes.metadata import JobExternalOutputMetadataWrapper from galaxy.managers import histories from galaxy.managers.jobs import JobSearch +from galaxy.managers.tags import GalaxyTagManager from galaxy.queue_worker import send_control_task from galaxy.tools.actions import DefaultToolAction from galaxy.tools.actions.data_manager import DataManagerToolAction @@ -2605,6 +2606,65 @@ def add_copied_value_to_new_elements(new_label, dce_object): ) +class TagFromFileTool(DatabaseOperationTool): + tool_type = 'tag_from_file' + + def produce_outputs(self, trans, out_data, output_collections, incoming, history, **kwds): + hdca = incoming["input"] + how = incoming['how'] + new_tags_dataset_assoc = incoming["tags"] + new_elements = odict() + tags_manager = GalaxyTagManager(trans.app.model.context) + + def add_copied_value_to_new_elements(new_tags_dict, dce): + if getattr(dce.element_object, "history_content_type", None) == "dataset": + copied_value = dce.element_object.copy() + # copy should never be visible, since part of a collection + copied_value.visble = False + history.add_dataset(copied_value, copied_value, set_hid=False) + new_tags = new_tags_dict.get(dce.element_identifier) + if new_tags: + if how in ('add', 'remove') and dce.element_object.tags: + # We need get the original tags and update them with the new tags + old_tags = set(tag for tag in tags_manager.get_tags_str(dce.element_object.tags).split(',') if tag) + if how == 'add': + old_tags.update(set(new_tags)) + elif how == 'remove': + old_tags = old_tags - set(new_tags) + new_tags = old_tags + tags_manager.add_tags_from_list(user=history.user, item=copied_value, new_tags_list=new_tags) + else: + # We have a collection, and we copy the elements so that we don't manipulate the original tags + copied_value = dce.element_object.copy(element_destination=history) + for new_element, old_element in zip(copied_value.dataset_elements, dce.element_object.dataset_elements): + # TODO: This should be eliminated, but collections created by the collection builder + # don't set `visible` to `False` if you don't hide the original elements. + new_element.element_object.visible = False + new_tags = new_tags_dict.get(new_element.element_identifier) + if how in ('add', 'remove'): + old_tags = set(tag for tag in tags_manager.get_tags_str(old_element.element_object.tags).split(',') if tag) + if new_tags: + if how == 'add': + old_tags.update(set(new_tags)) + elif how == 'remove': + old_tags = old_tags - set(new_tags) + new_tags = old_tags + tags_manager.add_tags_from_list(user=history.user, item=new_element.element_object, new_tags_list=new_tags) + new_elements[dce.element_identifier] = copied_value + + new_tags_path = new_tags_dataset_assoc.file_name + new_tags = open(new_tags_path, "r").readlines(1024 * 1000000) + # We have a tabular file, where the first column is an existing element identifier, + # and the remaining columns represent new tags. + source_new_tags = (line.strip().split('\t') for line in new_tags) + new_tags_dict = {item[0]: item[1:] for item in source_new_tags} + for i, dce in enumerate(hdca.collection.elements): + add_copied_value_to_new_elements(new_tags_dict, dce) + output_collections.create_collection( + next(iter(self.outputs.values())), "output", elements=new_elements + ) + + class FilterFromFileTool(DatabaseOperationTool): tool_type = 'filter_from_file' diff --git a/lib/galaxy/tools/tag_collection_from_file.xml b/lib/galaxy/tools/tag_collection_from_file.xml new file mode 100644 index 000000000000..36b2935a7b78 --- /dev/null +++ b/lib/galaxy/tools/tag_collection_from_file.xml @@ -0,0 +1,91 @@ + + from contents of a file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test-data/new_tags_1.txt b/test-data/new_tags_1.txt new file mode 100644 index 000000000000..91caa6c4fae3 --- /dev/null +++ b/test-data/new_tags_1.txt @@ -0,0 +1,2 @@ +forward orientation:forward alias:r1 alias:f +reverse orientation:reverse alias:r2 alias:r diff --git a/test/base/interactor.py b/test/base/interactor.py index 9ea5ed0318c2..7bf1c0d16a0f 100644 --- a/test/base/interactor.py +++ b/test/base/interactor.py @@ -107,12 +107,12 @@ def _verify_metadata(self, history_id, hid, attributes): """Check dataset metadata. ftype on output maps to `file_ext` on the hda's API description, `name`, `info`, - and `dbkey` all map to the API description directly. Other metadata attributes + `dbkey` and `tags` all map to the API description directly. Other metadata attributes are assumed to be datatype-specific and mapped with a prefix of `metadata_`. """ metadata = attributes.get('metadata', {}).copy() for key, value in metadata.copy().items(): - if key not in ['name', 'info']: + if key not in ['name', 'info', 'tags']: new_key = "metadata_%s" % key metadata[new_key] = metadata[key] del metadata[key] diff --git a/test/functional/tools/samples_tool_conf.xml b/test/functional/tools/samples_tool_conf.xml index 930ee0c35027..7548b2df3850 100644 --- a/test/functional/tools/samples_tool_conf.xml +++ b/test/functional/tools/samples_tool_conf.xml @@ -183,5 +183,6 @@ +