Loading lib/galaxy/managers/taggable.py +9 −5 Original line number Diff line number Diff line Loading @@ -7,7 +7,10 @@ Mixins for Taggable model managers and serializers. import logging from typing import Type from sqlalchemy import sql from sqlalchemy import ( func, sql, ) from galaxy import model from galaxy.model.tags import GalaxyTagHandler Loading Loading @@ -118,15 +121,16 @@ class TaggableFilterMixin: target_model = getattr(model, f"{class_name}TagAssociation") id_column = f"{target_model.table.name.rsplit('_tag_association')[0]}_id" column = target_model.table.c.user_tname + ":" + target_model.table.c.user_value lower_val = val.lower() # Ignore case if op == "eq": if ":" not in val: if ":" not in lower_val: # We require an exact match and the tag to look for has no user_value, # so we can't just concatenate user_tname, ':' and user_vale cond = target_model.table.c.user_tname == val cond = func.lower(target_model.table.c.user_tname) == lower_val else: cond = column == val cond = func.lower(column) == lower_val else: cond = column.contains(val, autoescape=True) cond = func.lower(column).contains(lower_val, autoescape=True) return sql.expression.and_(model_class.table.c.id == getattr(target_model.table.c, id_column), cond) return _create_tag_filter Loading lib/galaxy_test/api/test_datasets.py +37 −0 Original line number Diff line number Diff line Loading @@ -131,6 +131,43 @@ class DatasetsApiTestCase(ApiTestCase): index_response = self._get("datasets", payload).json() assert len(index_response) == 0 def test_search_by_tag_case_insensitive(self): history_id = self.dataset_populator.new_history() hda_id = self.dataset_populator.new_dataset(history_id)["id"] update_payload = { "tags": ["name:new_TAG", "cool:another_TAG"], } updated_hda = self._put(f"histories/{history_id}/contents/{hda_id}", update_payload, json=True).json() assert "name:new_TAG" in updated_hda["tags"] assert "cool:another_TAG" in updated_hda["tags"] payload = { "limit": 10, "offset": 0, "q": ["history_content_type", "tag"], "qv": ["dataset", "name:new_tag"], "history_id": history_id, } index_response = self._get("datasets", payload).json() assert len(index_response) == 1 payload = { "limit": 10, "offset": 0, "q": ["history_content_type", "tag-contains"], "qv": ["dataset", "new_tag"], "history_id": history_id, } index_response = self._get("datasets", payload).json() assert len(index_response) == 1 payload = { "limit": 10, "offset": 0, "q": ["history_content_type", "tag-contains"], "qv": ["dataset", "notag"], "history_id": history_id, } index_response = self._get("datasets", payload).json() assert len(index_response) == 0 def test_search_by_tool_id(self): self.dataset_populator.new_dataset(self.history_id) payload = { Loading Loading
lib/galaxy/managers/taggable.py +9 −5 Original line number Diff line number Diff line Loading @@ -7,7 +7,10 @@ Mixins for Taggable model managers and serializers. import logging from typing import Type from sqlalchemy import sql from sqlalchemy import ( func, sql, ) from galaxy import model from galaxy.model.tags import GalaxyTagHandler Loading Loading @@ -118,15 +121,16 @@ class TaggableFilterMixin: target_model = getattr(model, f"{class_name}TagAssociation") id_column = f"{target_model.table.name.rsplit('_tag_association')[0]}_id" column = target_model.table.c.user_tname + ":" + target_model.table.c.user_value lower_val = val.lower() # Ignore case if op == "eq": if ":" not in val: if ":" not in lower_val: # We require an exact match and the tag to look for has no user_value, # so we can't just concatenate user_tname, ':' and user_vale cond = target_model.table.c.user_tname == val cond = func.lower(target_model.table.c.user_tname) == lower_val else: cond = column == val cond = func.lower(column) == lower_val else: cond = column.contains(val, autoescape=True) cond = func.lower(column).contains(lower_val, autoescape=True) return sql.expression.and_(model_class.table.c.id == getattr(target_model.table.c, id_column), cond) return _create_tag_filter Loading
lib/galaxy_test/api/test_datasets.py +37 −0 Original line number Diff line number Diff line Loading @@ -131,6 +131,43 @@ class DatasetsApiTestCase(ApiTestCase): index_response = self._get("datasets", payload).json() assert len(index_response) == 0 def test_search_by_tag_case_insensitive(self): history_id = self.dataset_populator.new_history() hda_id = self.dataset_populator.new_dataset(history_id)["id"] update_payload = { "tags": ["name:new_TAG", "cool:another_TAG"], } updated_hda = self._put(f"histories/{history_id}/contents/{hda_id}", update_payload, json=True).json() assert "name:new_TAG" in updated_hda["tags"] assert "cool:another_TAG" in updated_hda["tags"] payload = { "limit": 10, "offset": 0, "q": ["history_content_type", "tag"], "qv": ["dataset", "name:new_tag"], "history_id": history_id, } index_response = self._get("datasets", payload).json() assert len(index_response) == 1 payload = { "limit": 10, "offset": 0, "q": ["history_content_type", "tag-contains"], "qv": ["dataset", "new_tag"], "history_id": history_id, } index_response = self._get("datasets", payload).json() assert len(index_response) == 1 payload = { "limit": 10, "offset": 0, "q": ["history_content_type", "tag-contains"], "qv": ["dataset", "notag"], "history_id": history_id, } index_response = self._get("datasets", payload).json() assert len(index_response) == 0 def test_search_by_tool_id(self): self.dataset_populator.new_dataset(self.history_id) payload = { Loading