diff --git a/client/src/components/Form/Elements/FormData/FormData.vue b/client/src/components/Form/Elements/FormData/FormData.vue
index f6054f28cd00..64b5d62dec2b 100644
--- a/client/src/components/Form/Elements/FormData/FormData.vue
+++ b/client/src/components/Form/Elements/FormData/FormData.vue
@@ -5,6 +5,7 @@ import { faCaretDown, faCaretUp, faExclamation, faLink, faUnlink } from "@fortaw
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { BAlert, BButton, BButtonGroup, BCollapse, BFormCheckbox, BTooltip } from "bootstrap-vue";
import { computed, onMounted, type Ref, ref, watch } from "vue";
+import HelpText from "@/components/Help/HelpText.vue";
import { getGalaxyInstance } from "@/app";
import { useDatatypesMapper } from "@/composables/datatypesMapper";
@@ -650,7 +651,10 @@ const noOptionsWarningMessage = computed(() => {
-
+
+ The supplied input will be this tool.
+
+
This is a batch mode input field. Individual jobs will be triggered for each dataset.
diff --git a/client/src/components/Help/HelpPopover.vue b/client/src/components/Help/HelpPopover.vue
new file mode 100644
index 000000000000..637dea8ebe95
--- /dev/null
+++ b/client/src/components/Help/HelpPopover.vue
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
diff --git a/client/src/components/Help/HelpText.vue b/client/src/components/Help/HelpText.vue
index f3f1b4740af1..24e166f97c78 100644
--- a/client/src/components/Help/HelpText.vue
+++ b/client/src/components/Help/HelpText.vue
@@ -1,9 +1,9 @@
-
-
-
+ :help="help" />
{{ text }}
{{ text }}
diff --git a/client/src/components/Help/terms.yml b/client/src/components/Help/terms.yml
index a6df4b852a63..6391c7979e18 100644
--- a/client/src/components/Help/terms.yml
+++ b/client/src/components/Help/terms.yml
@@ -29,6 +29,31 @@ unix:
More information on stack traces can be found on [Wikipedia](https://en.wikipedia.org/wiki/Stack_trace).
galaxy:
+ collections:
+ flatList: |
+ A flat list is just a simple dataset collection of type ``list`` that contains only datasets and not
+ other collections.
+ mapOver: |
+ When a tool consumes a dataset but is run with a collection, the collection *maps over* the collection.
+ This means instead of just running the tool once - the tool will be run once for each element of the
+ provided collection. Additionally, the outputs of the tool will be collected into a collection that
+ matches the structure of the provided collection. This matching structure means the output collections
+ will have the same element identifiers as the provided collection and they will appear in the same order.
+
+ It is easiest to visualize "mapping over" a collection is in the context of a tool that consumes a dataset
+ and produces a dataset, but the semantics apply rather naturally to tools that consume collections or
+ produce collections as well.
+
+ For instance, consider a tool that consumes a ``paired`` collection and produces an output dataset.
+ If a list of paired collections (collection type ``list:paired``) is passed to the tool - it will
+ will produce a flat list (collection type ``list``) of output datasets with the same number of elements
+ in the same order as the provided list of ``paired`` collections.
+
+ In the case of outputs, consider a tool that takes in a dataset and produces a flat list. If this tool
+ is run over a flat list of datasets - that list will be "mapped over" and each element will produce a list.
+ These lists will be gathered together in a nested list structured (collection type ``list:list``) where
+ the outer element count and structure matches that of the input and the inner list for each of those
+ is just the outputs of the tool for the corresponding element of the input.
jobs:
states:
# upload, waiting, failed, paused, deleting, deleted, stop, stopped, skipped.
diff --git a/client/src/components/Help/useHelp.ts b/client/src/components/Help/useHelp.ts
new file mode 100644
index 000000000000..d9fbd92f383e
--- /dev/null
+++ b/client/src/components/Help/useHelp.ts
@@ -0,0 +1,17 @@
+import { computed,type Ref } from "vue";
+
+import { hasHelp as hasHelpText, help as helpText } from "./terms";
+
+export function useHelp(
+ uri: Ref,
+) {
+ const hasHelp = computed(() => {
+ return hasHelpText(uri.value);
+ });
+
+ const help = computed(() => {
+ return helpText(uri.value) as string;
+ });
+
+ return { hasHelp, help };
+}
diff --git a/client/src/components/Tool/ToolCard.vue b/client/src/components/Tool/ToolCard.vue
index d28e4d46ad96..6c0876d3ac8c 100644
--- a/client/src/components/Tool/ToolCard.vue
+++ b/client/src/components/Tool/ToolCard.vue
@@ -186,7 +186,7 @@ const showHelpForum = computed(() => isConfigLoaded.value && config.value.enable
Help
-
+
-import { useFormattedToolHelp } from "composables/formattedToolHelp";
+
-
-
+
+
+
+
+ {{ content }}
+
+
-
-
diff --git a/client/src/components/Tool/ToolHelpMarkdown.vue b/client/src/components/Tool/ToolHelpMarkdown.vue
new file mode 100644
index 000000000000..15cee791dfb0
--- /dev/null
+++ b/client/src/components/Tool/ToolHelpMarkdown.vue
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/Tool/ToolHelp.test.js b/client/src/components/Tool/ToolHelpRst.test.js
similarity index 87%
rename from client/src/components/Tool/ToolHelp.test.js
rename to client/src/components/Tool/ToolHelpRst.test.js
index afbd19d4ba92..40f8e95f2d1e 100644
--- a/client/src/components/Tool/ToolHelp.test.js
+++ b/client/src/components/Tool/ToolHelpRst.test.js
@@ -1,7 +1,7 @@
import { mount } from "@vue/test-utils";
import { getLocalVue } from "tests/jest/helpers";
-import ToolHelp from "./ToolHelp";
+import ToolHelpRst from "./ToolHelpRst";
const localVue = getLocalVue();
@@ -25,9 +25,9 @@ const expectedHelpText = `
h4 Heading
empty link`;
-describe("ToolHelp", () => {
+describe("ToolHelp RST", () => {
it("modifies help text", () => {
- const wrapper = mount(ToolHelp, {
+ const wrapper = mount(ToolHelpRst, {
propsData: {
content: inputHelpText,
},
diff --git a/client/src/components/Tool/ToolHelpRst.vue b/client/src/components/Tool/ToolHelpRst.vue
new file mode 100644
index 000000000000..88420b8b2ee6
--- /dev/null
+++ b/client/src/components/Tool/ToolHelpRst.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
diff --git a/lib/galaxy/tool_util/parser/cwl.py b/lib/galaxy/tool_util/parser/cwl.py
index 571a7faa4172..6891dd9590c6 100644
--- a/lib/galaxy/tool_util/parser/cwl.py
+++ b/lib/galaxy/tool_util/parser/cwl.py
@@ -11,6 +11,7 @@
)
from galaxy.tool_util.deps import requirements
from .interface import (
+ HelpContent,
PageSource,
PagesSource,
ToolSource,
@@ -80,7 +81,11 @@ def parse_edam_topics(self):
return []
def parse_help(self):
- return self.tool_proxy.doc()
+ doc = self.tool_proxy.doc()
+ if doc:
+ return HelpContent(type="plain_text", content=doc)
+ else:
+ return None
def parse_sanitize(self):
return False
diff --git a/lib/galaxy/tool_util/parser/interface.py b/lib/galaxy/tool_util/parser/interface.py
index a7b3896c289f..f0f06103e80d 100644
--- a/lib/galaxy/tool_util/parser/interface.py
+++ b/lib/galaxy/tool_util/parser/interface.py
@@ -121,6 +121,11 @@ class Citation(BaseModel):
content: str
+class HelpContent(BaseModel):
+ type: Literal["restructuredtext", "plain_text", "markdown"]
+ content: str
+
+
class ToolSource(metaclass=ABCMeta):
"""This interface represents an abstract source to parse tool
information from.
@@ -333,9 +338,11 @@ def parse_stdio(self):
return [], []
@abstractmethod
- def parse_help(self) -> Optional[str]:
- """Return RST definition of help text for tool or None if the tool
- doesn't define help text.
+ def parse_help(self) -> Optional[HelpContent]:
+ """Return help text for tool or None if the tool doesn't define help text.
+
+ The returned object contains the help text and an indication if it ``rst``
+ or ``markdown``.
"""
@abstractmethod
diff --git a/lib/galaxy/tool_util/parser/xml.py b/lib/galaxy/tool_util/parser/xml.py
index 9ab4a30f65b6..a84ece1156c0 100644
--- a/lib/galaxy/tool_util/parser/xml.py
+++ b/lib/galaxy/tool_util/parser/xml.py
@@ -41,6 +41,7 @@
DrillDownDynamicOptions,
DrillDownOptionsDict,
DynamicOptions,
+ HelpContent,
InputSource,
PageSource,
PagesSource,
@@ -649,9 +650,14 @@ def parse_strict_shell(self):
else:
return string_as_bool(default)
- def parse_help(self):
+ def parse_help(self) -> Optional[HelpContent]:
help_elem = self.root.find("help")
- return help_elem.text if help_elem is not None else None
+ if help_elem is None:
+ return None
+
+ help_type = help_elem.get("type", "restructuredtext")
+ content = help_elem.text
+ return HelpContent(type=help_type, content=content)
@property
def macro_paths(self):
diff --git a/lib/galaxy/tool_util/parser/yaml.py b/lib/galaxy/tool_util/parser/yaml.py
index 6813db0f5211..1f3f7976e724 100644
--- a/lib/galaxy/tool_util/parser/yaml.py
+++ b/lib/galaxy/tool_util/parser/yaml.py
@@ -125,7 +125,11 @@ def parse_stdio(self):
return error_on_exit_code()
def parse_help(self):
- return self.root_dict.get("help", None)
+ content = self.root_dict.get("help", None)
+ if content:
+ return HelpContent(type="markdown", content=content)
+ else:
+ return None
def parse_outputs(self, tool):
outputs = self.root_dict.get("outputs", {})
diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py
index 7193fd6ea618..81d6940db560 100644
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -78,6 +78,7 @@
ToolOutputCollectionPart,
)
from galaxy.tool_util.parser.interface import (
+ HelpContent,
InputSource,
PageSource,
ToolSource,
@@ -1678,9 +1679,10 @@ def populate_tool_shed_info(self, tool_shed_repository):
@property
def help(self) -> Template:
+ assert self.raw_help.type == "restructuredtext"
try:
return Template(
- rst_to_html(self.raw_help),
+ rst_to_html(self.raw_help.content),
input_encoding="utf-8",
default_filters=["decode.utf8"],
encoding_errors="replace",
@@ -1697,23 +1699,25 @@ def biotools_reference(self) -> Optional[str]:
"""
return biotools_reference(self.xrefs)
- def __get_help_with_images(self, raw_help: Optional[str]):
- help_text = raw_help or ""
- try:
- if help_text.find(".. image:: ") >= 0 and (self.tool_shed_repository or self.repository_id):
- return set_image_paths(
- self.app,
- help_text,
- encoded_repository_id=self.repository_id,
- tool_shed_repository=self.tool_shed_repository,
- tool_id=self.old_id,
- tool_version=self.version,
+ def __get_help_with_images(self, help_content: Optional[HelpContent]) -> Optional[HelpContent]:
+ if help_content and help_content.type == "restructuredtext":
+ help_text = help_content.content or ""
+ try:
+ if help_text.find(".. image:: ") >= 0 and (self.tool_shed_repository or self.repository_id):
+ help_text = set_image_paths(
+ self.app,
+ help_text,
+ encoded_repository_id=self.repository_id,
+ tool_shed_repository=self.tool_shed_repository,
+ tool_id=self.old_id,
+ tool_version=self.version,
+ )
+ except Exception:
+ log.exception(
+ "Exception in parse_help, so images may not be properly displayed for tool with id '%s'", self.id
)
- except Exception:
- log.exception(
- "Exception in parse_help, so images may not be properly displayed for tool with id '%s'", self.id
- )
- return help_text
+ help_content = HelpContent(type="restructuredtext", content=help_text)
+ return help_content
def find_output_def(self, name):
# name is JobToOutputDatasetAssociation name.
@@ -2505,12 +2509,17 @@ def to_dict(self, trans, link_details=False, io_details=False, tool_help=False):
if tool_help:
# create tool help
help_txt = ""
- if self.help:
- help_txt = self.help.render(
- static_path=self.app.url_for("/static"), host_url=self.app.url_for("/", qualified=True)
- )
- help_txt = unicodify(help_txt)
+ help_type = "restructuredtext"
+ help_content = self.raw_help
+ if help_content:
+ if help_content.type == "restructuredtext":
+ help_txt = self.help.render(
+ static_path=self.app.url_for("/static"), host_url=self.app.url_for("/", qualified=True)
+ )
+ help_txt = unicodify(help_txt)
+
tool_dict["help"] = help_txt
+ tool_dict["help_type"] = help_content.type
return tool_dict
@@ -2570,11 +2579,15 @@ def to_json(self, trans, kwd=None, job=None, workflow_building_mode=False, histo
# create tool help
tool_help = ""
- if self.help:
+ tool_help_type = "restructuredtext"
+ if self.raw_help and self.raw_help.type == "restructuredtext":
tool_help = self.help.render(
static_path=self.app.url_for("/static"), host_url=self.app.url_for("/", qualified=True)
)
tool_help = unicodify(tool_help, "utf-8")
+ elif self.raw_help:
+ tool_help = self.raw_help.content
+ tool_help_type = self.raw_help.type
if isinstance(self.action, tuple):
action = self.action[0] + self.app.url_for(self.action[1])
@@ -2586,6 +2599,7 @@ def to_json(self, trans, kwd=None, job=None, workflow_building_mode=False, histo
{
"id": self.id,
"help": tool_help,
+ "help_type": tool_help_type,
"citations": bool(self.citations),
"sharable_url": self.sharable_url,
"message": tool_message,
diff --git a/lib/galaxy/workflow/modules.py b/lib/galaxy/workflow/modules.py
index eeece34fe554..a74a59923114 100644
--- a/lib/galaxy/workflow/modules.py
+++ b/lib/galaxy/workflow/modules.py
@@ -1854,7 +1854,7 @@ def get_version(self):
return self.tool.version if self.tool else self.tool_version
def get_tooltip(self, static_path=None):
- if self.tool and self.tool.help:
+ if self.tool and self.tool.raw_help and self.tool.raw_help.type == "restructuredtext":
host_url = self.trans.url_builder("/")
static_path = self.trans.url_builder(static_path) if static_path else ""
return self.tool.help.render(host_url=host_url, static_path=static_path)