diff --git a/lib/galaxy/tool_util/toolbox/views/definitions.py b/lib/galaxy/tool_util/toolbox/views/definitions.py index 8f10c548550..735bcd9286b 100644 --- a/lib/galaxy/tool_util/toolbox/views/definitions.py +++ b/lib/galaxy/tool_util/toolbox/views/definitions.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import List, Optional, Union +from typing import Any, cast, List, Optional, Union from pydantic import BaseModel, Field from typing_extensions import Literal @@ -33,7 +33,7 @@ OptionalExclusionList = Optional[List[Exclusions]] class Tool(BaseModel): - content_type: Literal['tool'] = Field(alias="type") + content_type: Literal['tool'] = Field("tool", alias="type") id: str class Config: @@ -78,13 +78,18 @@ SectionContent = Union[ class HasItems: + items: Optional[List[Any]] @property - def items_expanded(self): + def items_expanded(self) -> Optional[List['ExpandedRootContent']]: + if self.items is None: + return None + # replace SectionAliases with individual SectionAlias objects # replace LabelShortcuts with Labels - items = [] + items: List[ExpandedRootContent] = [] for item in self.items: + item = cast(RootContent, item) if isinstance(item, SectionAliases): for section in item.sections: section_alias = SectionAlias( @@ -138,14 +143,23 @@ RootContent = Union[ ItemsFrom, ] +ExpandedRootContent = Union[ + Section, + SectionAlias, + Tool, + Label, + Workflow, + ItemsFrom, +] + class StaticToolBoxView(BaseModel, HasItems): id: str name: str description: Optional[str] view_type: StaticToolBoxViewTypeEnum = Field(alias="type") - items: List[RootContent] - excludes: Optional[List[Exclusions]] + items: Optional[List[RootContent]] # if empty, use integrated tool panel + excludes: OptionalExclusionList @staticmethod def from_dict(as_dict): diff --git a/lib/galaxy/tool_util/toolbox/views/static.py b/lib/galaxy/tool_util/toolbox/views/static.py index 60c094d3ee8..7bf5612ba76 100644 --- a/lib/galaxy/tool_util/toolbox/views/static.py +++ b/lib/galaxy/tool_util/toolbox/views/static.py @@ -1,12 +1,17 @@ import logging import re +from typing import Optional from .definitions import ( ExcludeTool, ExcludeToolRegex, ExcludeTypes, + ExpandedRootContent, + Label, Section, + SectionAlias, StaticToolBoxView, + Tool, Workflow, ) from .interface import ( @@ -16,6 +21,7 @@ from .interface import ( ToolPanelViewModelType, ) from ..panel import ( + panel_item_types, ToolPanelElements, ToolSection, ToolSectionLabel, @@ -65,12 +71,15 @@ class StaticToolPanelView(ToolPanelView): def apply_view(self, base_tool_panel: ToolPanelElements, toolbox_registry: ToolBoxRegistry) -> ToolPanelElements: def apply_filter(definition, elems): - if definition.excludes: - elems.apply_filter(build_filter(definition.excludes)) + excludes = self._all_excludes(definition) + if excludes: + elems.apply_filter(build_filter(excludes)) - def definition_with_items_to_panel(definition, allow_sections: bool = True): + def definition_with_items_to_panel(definition, allow_sections: bool = True, items=None): new_panel = ToolPanelElements() - for element in definition.items_expanded: + if items is None: + items = definition.items_expanded + for element in items: if element.content_type == "section": assert allow_sections section_def: Section = element @@ -145,12 +154,47 @@ class StaticToolPanelView(ToolPanelView): else: raise AssertionError("Unknown static toolbox configuration element encountered.") - if definition.excludes: - new_panel.apply_filter(build_filter(definition.excludes)) + excludes = self._all_excludes(definition) + if excludes: + new_panel.apply_filter(build_filter(excludes)) return new_panel - return definition_with_items_to_panel(self._definition) + root_defintion = self._definition + root_items = root_defintion.items_expanded + if root_items is None: + root_items = [] + # No items found, use base tool panel and apply filters to that... + for (_, panel_type, panel_value) in base_tool_panel.panel_items_iter(): + item: Optional[ExpandedRootContent] = None + if panel_type == panel_item_types.TOOL: + item = Tool( + id=panel_value.id, + ) + elif panel_type == panel_item_types.SECTION: + item = SectionAlias( + section=panel_value.id, + ) + elif panel_type == panel_item_types.LABEL: + item = Label( + id=panel_value.id, + text=panel_value.text, + ) + elif panel_type == panel_item_types.WORKFLOW: + item = Workflow( + id=panel_value.id, + ) + if item is None: + raise Exception("Unknown panel item type encountered.") + root_items.append(item) + + return definition_with_items_to_panel(root_defintion, items=root_items) + + def _all_excludes(self, has_excludes): + excludes = has_excludes.excludes or [] + if has_excludes != self._definition and self._definition.excludes: + excludes.extend(self._definition.excludes) + return excludes def to_model(self) -> ToolPanelViewModel: model_id = self._definition.id diff --git a/test/integration/panel_views_1/custom_11.yml b/test/integration/panel_views_1/custom_11.yml new file mode 100644 index 00000000000..480d992c244 --- /dev/null +++ b/test/integration/panel_views_1/custom_11.yml @@ -0,0 +1,7 @@ +name: Globally Applied Filters +type: activity +items: +- section: test +excludes: +- tool_id_regex: 'multi_data_.*' +- tool_id_regex: '.*_text_option' diff --git a/test/integration/panel_views_1/custom_12.yml b/test/integration/panel_views_1/custom_12.yml new file mode 100644 index 00000000000..eeec5378690 --- /dev/null +++ b/test/integration/panel_views_1/custom_12.yml @@ -0,0 +1,5 @@ +name: Globally Applied Filters +type: activity +excludes: +- tool_id_regex: 'multi_data_.*' +- tool_id_regex: '.*_text_option' diff --git a/test/integration/test_panel_views.py b/test/integration/test_panel_views.py index 38fcb413ef2..1082dd35a30 100644 --- a/test/integration/test_panel_views.py +++ b/test/integration/test_panel_views.py @@ -95,6 +95,21 @@ class PanelViewsFromDirectoryIntegrationTestCase(integration_util.IntegrationTes assert len(index_as_list) == 2 assert model_classes(index_as_list) == ["ToolSection", "ToolSection"] + def test_global_filters(self): + index = self.galaxy_interactor.get("tools", data=dict(in_panel=True, view="custom_11")) + verify_custom_regex_filtered(index) + + def test_global_filters_on_integrated_panel(self): + index = self.galaxy_interactor.get("tools", data=dict(in_panel=True, view="custom_12")) + index.raise_for_status() + index_as_list = index.json() + sections = [x for x in index_as_list if x["model_class"] == "ToolSection"] + assert len(sections) == 2 + section = sections[0] + assert section["id"] == "test" + tools = section["elems"] + assert len(tools) == 2, len(tools) + class PanelViewsFromConfigIntegrationTestCase(integration_util.IntegrationTestCase):