From 8c95c2fdb0a0206e20f0792aa001a1acdf67b48b Mon Sep 17 00:00:00 2001 From: John Chilton Date: Mon, 13 Sep 2021 11:04:53 -0400 Subject: [PATCH 1/5] Apply static tool panel exclusions at the root globally. Makes it really clean to just exclude a bunch of tools across the whole panel, previously you'd need to deep tracking of what sections to exclude them from which might vary from Galaxy to Galaxy for instance. --- .../tool_util/toolbox/views/definitions.py | 2 +- lib/galaxy/tool_util/toolbox/views/static.py | 17 +++++++++++++---- test/integration/panel_views_1/custom_11.yml | 7 +++++++ test/integration/test_panel_views.py | 4 ++++ 4 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 test/integration/panel_views_1/custom_11.yml diff --git a/lib/galaxy/tool_util/toolbox/views/definitions.py b/lib/galaxy/tool_util/toolbox/views/definitions.py index 8f10c548550..1eef6c6cb45 100644 --- a/lib/galaxy/tool_util/toolbox/views/definitions.py +++ b/lib/galaxy/tool_util/toolbox/views/definitions.py @@ -145,7 +145,7 @@ class StaticToolBoxView(BaseModel, HasItems): description: Optional[str] view_type: StaticToolBoxViewTypeEnum = Field(alias="type") items: List[RootContent] - excludes: Optional[List[Exclusions]] + 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..da124cf92b8 100644 --- a/lib/galaxy/tool_util/toolbox/views/static.py +++ b/lib/galaxy/tool_util/toolbox/views/static.py @@ -5,6 +5,7 @@ from .definitions import ( ExcludeTool, ExcludeToolRegex, ExcludeTypes, + Exclusions, Section, StaticToolBoxView, Workflow, @@ -65,8 +66,9 @@ 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): new_panel = ToolPanelElements() @@ -145,13 +147,20 @@ 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) + 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 name = self._definition.name 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/test_panel_views.py b/test/integration/test_panel_views.py index 38fcb413ef2..6fa1d989bd7 100644 --- a/test/integration/test_panel_views.py +++ b/test/integration/test_panel_views.py @@ -95,6 +95,10 @@ 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) + class PanelViewsFromConfigIntegrationTestCase(integration_util.IntegrationTestCase): From 29c545752b1842dcadb711ad301641e62f22e92f Mon Sep 17 00:00:00 2001 From: John Chilton Date: Mon, 13 Sep 2021 11:46:19 -0400 Subject: [PATCH 2/5] Allow static panel views without explicit "items" - just start with integrated panel and apply filters. --- .../tool_util/toolbox/views/definitions.py | 23 +++++++--- lib/galaxy/tool_util/toolbox/views/static.py | 43 +++++++++++++++++-- test/integration/panel_views_1/custom_12.yml | 5 +++ test/integration/test_panel_views.py | 11 +++++ 4 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 test/integration/panel_views_1/custom_12.yml diff --git a/lib/galaxy/tool_util/toolbox/views/definitions.py b/lib/galaxy/tool_util/toolbox/views/definitions.py index 1eef6c6cb45..214396cc4c7 100644 --- a/lib/galaxy/tool_util/toolbox/views/definitions.py +++ b/lib/galaxy/tool_util/toolbox/views/definitions.py @@ -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,12 +78,16 @@ SectionContent = Union[ class HasItems: + items: Optional[List['RootContent']] @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: if isinstance(item, SectionAliases): for section in item.sections: @@ -108,7 +112,7 @@ class Section(BaseModel, HasItems): content_type: Literal['section'] = Field(alias="type") id: Optional[str] name: Optional[str] - items: Optional[List[SectionContent]] + items: Optional[List[RootContent]] # really is just SectionContent but would need to use type variables to represent that. excludes: OptionalExclusionList class Config: @@ -138,13 +142,22 @@ 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] + items: Optional[List[RootContent]] # if empty, use integrated tool panel excludes: OptionalExclusionList @staticmethod diff --git a/lib/galaxy/tool_util/toolbox/views/static.py b/lib/galaxy/tool_util/toolbox/views/static.py index da124cf92b8..7bf5612ba76 100644 --- a/lib/galaxy/tool_util/toolbox/views/static.py +++ b/lib/galaxy/tool_util/toolbox/views/static.py @@ -1,13 +1,17 @@ import logging import re +from typing import Optional from .definitions import ( ExcludeTool, ExcludeToolRegex, ExcludeTypes, - Exclusions, + ExpandedRootContent, + Label, Section, + SectionAlias, StaticToolBoxView, + Tool, Workflow, ) from .interface import ( @@ -17,6 +21,7 @@ from .interface import ( ToolPanelViewModelType, ) from ..panel import ( + panel_item_types, ToolPanelElements, ToolSection, ToolSectionLabel, @@ -70,9 +75,11 @@ class StaticToolPanelView(ToolPanelView): 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 @@ -153,7 +160,35 @@ class StaticToolPanelView(ToolPanelView): 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 [] 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 6fa1d989bd7..1082dd35a30 100644 --- a/test/integration/test_panel_views.py +++ b/test/integration/test_panel_views.py @@ -99,6 +99,17 @@ class PanelViewsFromDirectoryIntegrationTestCase(integration_util.IntegrationTes 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): From 6378157253c03139e80d90d806a886d9ad2a775f Mon Sep 17 00:00:00 2001 From: John Chilton Date: Tue, 14 Sep 2021 11:17:11 -0400 Subject: [PATCH 3/5] Update lib/galaxy/tool_util/toolbox/views/definitions.py Co-authored-by: Marius van den Beek --- lib/galaxy/tool_util/toolbox/views/definitions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/tool_util/toolbox/views/definitions.py b/lib/galaxy/tool_util/toolbox/views/definitions.py index 214396cc4c7..8071d69cdb4 100644 --- a/lib/galaxy/tool_util/toolbox/views/definitions.py +++ b/lib/galaxy/tool_util/toolbox/views/definitions.py @@ -112,7 +112,7 @@ class Section(BaseModel, HasItems): content_type: Literal['section'] = Field(alias="type") id: Optional[str] name: Optional[str] - items: Optional[List[RootContent]] # really is just SectionContent but would need to use type variables to represent that. + items: Optional[List['RootContent']] # really is just SectionContent but would need to use type variables to represent that. excludes: OptionalExclusionList class Config: From 098876b69a38d11c70c79a55d4126b718679806a Mon Sep 17 00:00:00 2001 From: John Chilton Date: Wed, 15 Sep 2021 11:09:45 -0400 Subject: [PATCH 4/5] Use generics I guess? Learning Python typing as we go here... --- lib/galaxy/tool_util/toolbox/views/definitions.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/galaxy/tool_util/toolbox/views/definitions.py b/lib/galaxy/tool_util/toolbox/views/definitions.py index 8071d69cdb4..a2acfb2ae2b 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 Generic, List, Optional, TypeVar, Union from pydantic import BaseModel, Field from typing_extensions import Literal @@ -77,8 +77,11 @@ SectionContent = Union[ ] -class HasItems: - items: Optional[List['RootContent']] +C = TypeVar('C', bound="RootContent") + + +class HasItems(Generic[C]): + items: Optional[List[C]] @property def items_expanded(self) -> Optional[List['ExpandedRootContent']]: @@ -108,11 +111,11 @@ class HasItems: return items -class Section(BaseModel, HasItems): +class Section(BaseModel, HasItems[SectionContent]): content_type: Literal['section'] = Field(alias="type") id: Optional[str] name: Optional[str] - items: Optional[List['RootContent']] # really is just SectionContent but would need to use type variables to represent that. + items: Optional[List[SectionContent]] # really is just SectionContent but would need to use type variables to represent that. excludes: OptionalExclusionList class Config: @@ -152,7 +155,7 @@ ExpandedRootContent = Union[ ] -class StaticToolBoxView(BaseModel, HasItems): +class StaticToolBoxView(BaseModel, HasItems[RootContent]): id: str name: str description: Optional[str] From 9ef2c86632ff5602b6544a2224618cd288bd4bd5 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Wed, 15 Sep 2021 12:16:40 -0400 Subject: [PATCH 5/5] Break typing for Pydantic on Python 3.6. --- .../tool_util/toolbox/views/definitions.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/galaxy/tool_util/toolbox/views/definitions.py b/lib/galaxy/tool_util/toolbox/views/definitions.py index a2acfb2ae2b..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 Generic, List, Optional, TypeVar, Union +from typing import Any, cast, List, Optional, Union from pydantic import BaseModel, Field from typing_extensions import Literal @@ -77,11 +77,8 @@ SectionContent = Union[ ] -C = TypeVar('C', bound="RootContent") - - -class HasItems(Generic[C]): - items: Optional[List[C]] +class HasItems: + items: Optional[List[Any]] @property def items_expanded(self) -> Optional[List['ExpandedRootContent']]: @@ -92,6 +89,7 @@ class HasItems(Generic[C]): # replace LabelShortcuts with Labels items: List[ExpandedRootContent] = [] for item in self.items: + item = cast(RootContent, item) if isinstance(item, SectionAliases): for section in item.sections: section_alias = SectionAlias( @@ -111,11 +109,11 @@ class HasItems(Generic[C]): return items -class Section(BaseModel, HasItems[SectionContent]): +class Section(BaseModel, HasItems): content_type: Literal['section'] = Field(alias="type") id: Optional[str] name: Optional[str] - items: Optional[List[SectionContent]] # really is just SectionContent but would need to use type variables to represent that. + items: Optional[List[SectionContent]] excludes: OptionalExclusionList class Config: @@ -155,7 +153,7 @@ ExpandedRootContent = Union[ ] -class StaticToolBoxView(BaseModel, HasItems[RootContent]): +class StaticToolBoxView(BaseModel, HasItems): id: str name: str description: Optional[str]