mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-16 07:19:13 +08:00
markdown embed
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
:title="title"
|
||||
:page-id="pageId"
|
||||
:public-url="publicUrl"
|
||||
:content="content"
|
||||
:content="contentEditor"
|
||||
:content-data="contentData" />
|
||||
</template>
|
||||
|
||||
@@ -24,6 +24,7 @@ interface PageData {
|
||||
title: string;
|
||||
content: string;
|
||||
content_format: string;
|
||||
content_editor: string;
|
||||
username: string;
|
||||
slug: string;
|
||||
}
|
||||
@@ -34,6 +35,7 @@ const props = defineProps<{
|
||||
|
||||
const title = ref("");
|
||||
const content = ref<string>("");
|
||||
const contentEditor = ref<string>("");
|
||||
const contentFormat = ref<string>("");
|
||||
const contentData = ref<PageData>();
|
||||
const publicUrl = ref<string>("");
|
||||
@@ -60,6 +62,7 @@ const loadPage = async () => {
|
||||
if (data) {
|
||||
publicUrl.value = `${getAppRoot()}u/${data.username}/p/${data.slug}`;
|
||||
content.value = data.content;
|
||||
contentEditor.value = data.content_editor;
|
||||
contentFormat.value = data.content_format;
|
||||
contentData.value = data || {};
|
||||
title.value = data.title;
|
||||
|
||||
@@ -67,8 +67,25 @@ VALID_ARGUMENTS: dict[str, Union[list[str], DynamicArguments]] = {
|
||||
"workflow_image": ["invocation_id", "workflow_checkpoint", "workflow_id", "size"],
|
||||
"workflow_license": ["invocation_id", "workflow_id"],
|
||||
}
|
||||
EMBED_CAPABLE_DIRECTIVES = [
|
||||
"history_dataset_name",
|
||||
"history_dataset_type",
|
||||
"workflow_license",
|
||||
"invocation_time",
|
||||
"generate_time",
|
||||
"generate_galaxy_version",
|
||||
"instance_access_link",
|
||||
"instance_resources_link",
|
||||
"instance_help_link",
|
||||
"instance_support_link",
|
||||
"instance_citation_link",
|
||||
"instance_terms_link",
|
||||
"instance_organization_link",
|
||||
]
|
||||
|
||||
GALAXY_FLAVORED_MARKDOWN_CONTAINERS = list(VALID_ARGUMENTS.keys())
|
||||
GALAXY_FLAVORED_MARKDOWN_CONTAINER_REGEX = r"(?P<container>{})".format("|".join(GALAXY_FLAVORED_MARKDOWN_CONTAINERS))
|
||||
GALAXY_FLAVORED_MARKDOWN_EMBED_CONTAIN_REGEX = r"(?P<container>{})".format("|".join(EMBED_CAPABLE_DIRECTIVES))
|
||||
|
||||
ARG_VAL_REGEX = r"""[\w_\-]+|\"[^\"]+\"|\'[^\']+\'"""
|
||||
FUNCTION_ARG = rf"\s*[\w\|]+\s*=\s*(?:{ARG_VAL_REGEX})\s*"
|
||||
@@ -79,21 +96,15 @@ FUNCTION_CALL_LINE_TEMPLATE = f"\\s*%s\\s*\\((?:{FUNCTION_MULTIPLE_ARGS})?\\)\\s
|
||||
GALAXY_MARKDOWN_FUNCTION_CALL_LINE = re.compile(FUNCTION_CALL_LINE_TEMPLATE % GALAXY_FLAVORED_MARKDOWN_CONTAINER_REGEX)
|
||||
WHITE_SPACE_ONLY_PATTERN = re.compile(r"^[\s]+$")
|
||||
|
||||
GALAXY_MARKDOWN_EMBED_FUNCTION_CALL_LINE = FUNCTION_CALL_LINE_TEMPLATE % GALAXY_FLAVORED_MARKDOWN_EMBED_CONTAIN_REGEX
|
||||
GALAXY_MARKDOWN_EMBED_FUNCTION_CALL_LINE_PATT = re.compile(GALAXY_MARKDOWN_EMBED_FUNCTION_CALL_LINE)
|
||||
EMBED_DIRECTIVE_REGEX = re.compile(r"\$\{galaxy\s+%s\}" % GALAXY_MARKDOWN_EMBED_FUNCTION_CALL_LINE) # noqa: UP031
|
||||
EMBED_DIRECTIVE_REGEX_ANY = re.compile(r"\$\{galaxy\s+.*\}")
|
||||
|
||||
|
||||
def validate_galaxy_markdown(galaxy_markdown, internal=True):
|
||||
"""Validate the supplied markdown and throw an ValueError with reason if invalid."""
|
||||
|
||||
def invalid_line(template, line_no: int, **kwd):
|
||||
if "line" in kwd:
|
||||
kwd["line"] = kwd["line"].rstrip("\r\n")
|
||||
raise ValueError(f"Invalid line {line_no + 1}: {template.format(**kwd)}")
|
||||
|
||||
def _validate_arg(arg_str, valid_args, line_no: int):
|
||||
if arg_str is not None:
|
||||
arg_name = arg_str.split("=", 1)[0].strip()
|
||||
if arg_name not in valid_args and arg_name not in SHARED_ARGUMENTS:
|
||||
invalid_line("Invalid argument to Galaxy directive [{argument}]", line_no, argument=arg_name)
|
||||
|
||||
expecting_container_close_for = None
|
||||
last_line_no = 0
|
||||
function_calls = 0
|
||||
@@ -102,7 +113,7 @@ def validate_galaxy_markdown(galaxy_markdown, internal=True):
|
||||
|
||||
expecting_container_close = expecting_container_close_for is not None
|
||||
if not fenced and expecting_container_close:
|
||||
invalid_line(
|
||||
_invalid_line(
|
||||
"[{line}] is not expected close line for [{expected_for}]",
|
||||
line_no,
|
||||
line=line,
|
||||
@@ -110,7 +121,17 @@ def validate_galaxy_markdown(galaxy_markdown, internal=True):
|
||||
)
|
||||
continue
|
||||
elif not fenced:
|
||||
continue
|
||||
first_match_any = EMBED_DIRECTIVE_REGEX_ANY.search(line)
|
||||
first_match = EMBED_DIRECTIVE_REGEX.search(line)
|
||||
if first_match_any:
|
||||
if not first_match:
|
||||
_invalid_line(
|
||||
"[{line}] contains invalid template expansion",
|
||||
line_no,
|
||||
line=line,
|
||||
)
|
||||
else:
|
||||
_check_func_call(first_match, line_no)
|
||||
elif fenced and expecting_container_close and BLOCK_FENCE_END.match(line):
|
||||
# reset
|
||||
expecting_container_close_for = None
|
||||
@@ -118,7 +139,7 @@ def validate_galaxy_markdown(galaxy_markdown, internal=True):
|
||||
elif open_fence and GALAXY_FLAVORED_MARKDOWN_CONTAINER_LINE_PATTERN.match(line):
|
||||
if expecting_container_close:
|
||||
if not VALID_CONTAINER_END_PATTERN.match(line):
|
||||
invalid_line(
|
||||
_invalid_line(
|
||||
"Invalid command close line [{line}] for [{expected_for}]",
|
||||
line_no,
|
||||
line=line,
|
||||
@@ -136,28 +157,10 @@ def validate_galaxy_markdown(galaxy_markdown, internal=True):
|
||||
if func_call_match:
|
||||
function_calls += 1
|
||||
if function_calls > 1:
|
||||
invalid_line("Only one Galaxy directive is allowed per fenced Galaxy block (```galaxy)", line_no)
|
||||
container = func_call_match.group("container")
|
||||
valid_args = VALID_ARGUMENTS[container]
|
||||
if isinstance(valid_args, DynamicArguments):
|
||||
continue
|
||||
|
||||
first_arg_call = func_call_match.group("firstargcall")
|
||||
|
||||
_validate_arg(first_arg_call, valid_args, line_no)
|
||||
rest = func_call_match.group("restargcalls")
|
||||
while rest:
|
||||
rest = rest.strip().split(",", 1)[1]
|
||||
arg_match = FUNCTION_MULTIPLE_ARGS_PATTERN.match(rest)
|
||||
if not arg_match:
|
||||
break
|
||||
first_arg_call = arg_match.group("firstargcall")
|
||||
_validate_arg(first_arg_call, valid_args, line_no)
|
||||
rest = arg_match.group("restargcalls")
|
||||
|
||||
continue
|
||||
_invalid_line("Only one Galaxy directive is allowed per fenced Galaxy block (```galaxy)", line_no)
|
||||
_check_func_call(func_call_match, line_no)
|
||||
else:
|
||||
invalid_line("Invalid embedded Galaxy markup line [{line}]", line_no, line=line)
|
||||
_invalid_line("Invalid embedded Galaxy markup line [{line}]", line_no, line=line)
|
||||
|
||||
# Markdown unrelated to Galaxy object containers.
|
||||
continue
|
||||
@@ -168,6 +171,39 @@ def validate_galaxy_markdown(galaxy_markdown, internal=True):
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
def _invalid_line(template: str, line_no: int, **kwd):
|
||||
if "line" in kwd:
|
||||
kwd["line"] = kwd["line"].rstrip("\r\n")
|
||||
raise ValueError(f"Invalid line {line_no + 1}: {template.format(**kwd)}")
|
||||
|
||||
|
||||
def _validate_arg(arg_str: str, valid_args, line_no: int):
|
||||
if arg_str is not None:
|
||||
arg_name = arg_str.split("=", 1)[0].strip()
|
||||
if arg_name not in valid_args and arg_name not in SHARED_ARGUMENTS:
|
||||
_invalid_line("Invalid argument to Galaxy directive [{argument}]", line_no, argument=arg_name)
|
||||
|
||||
|
||||
def _check_func_call(func_call_match, line_no):
|
||||
container = func_call_match.group("container")
|
||||
valid_args = VALID_ARGUMENTS[container]
|
||||
if isinstance(valid_args, DynamicArguments):
|
||||
return
|
||||
|
||||
first_arg_call = func_call_match.group("firstargcall")
|
||||
|
||||
_validate_arg(first_arg_call, valid_args, line_no)
|
||||
rest = func_call_match.group("restargcalls")
|
||||
while rest:
|
||||
rest = rest.strip().split(",", 1)[1]
|
||||
arg_match = FUNCTION_MULTIPLE_ARGS_PATTERN.match(rest)
|
||||
if not arg_match:
|
||||
break
|
||||
first_arg_call = arg_match.group("firstargcall")
|
||||
_validate_arg(first_arg_call, valid_args, line_no)
|
||||
rest = arg_match.group("restargcalls")
|
||||
|
||||
|
||||
def _split_markdown_lines(markdown):
|
||||
"""Yield lines of a markdown document line-by-line keeping track of fencing.
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import os
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
from re import Match
|
||||
from typing import (
|
||||
Any,
|
||||
@@ -58,6 +59,7 @@ from galaxy.util.markdown import literal_via_fence
|
||||
from galaxy.util.resources import resource_string
|
||||
from galaxy.util.sanitize_html import sanitize_html
|
||||
from .markdown_parse import (
|
||||
EMBED_DIRECTIVE_REGEX,
|
||||
GALAXY_MARKDOWN_FUNCTION_CALL_LINE,
|
||||
validate_galaxy_markdown,
|
||||
)
|
||||
@@ -118,7 +120,19 @@ def ready_galaxy_markdown_for_import(trans, external_galaxy_markdown):
|
||||
line = line.replace(id_match.group(), f"{id_match.group(1)}={decoded_id}")
|
||||
return (line, False)
|
||||
|
||||
def _remap_embed_container(match):
|
||||
object_id: Optional[str] = None
|
||||
|
||||
whole_match = match.group()
|
||||
if id_match := re.search(ENCODED_ID_PATTERN, whole_match):
|
||||
object_id = id_match.group(2)
|
||||
decoded_id = trans.security.decode_id(object_id)
|
||||
whole_match = whole_match.replace(id_match.group(), f"{id_match.group(1)}={decoded_id}")
|
||||
|
||||
return whole_match
|
||||
|
||||
internal_markdown = _remap_galaxy_markdown_calls(_remap, external_galaxy_markdown)
|
||||
internal_markdown = _remap_galaxy_markdown_embedded_containers(_remap_embed_container, internal_markdown)
|
||||
internal_markdown = process_invocation_ids(trans.security.decode_id, internal_markdown)
|
||||
return internal_markdown
|
||||
|
||||
@@ -278,8 +292,80 @@ class GalaxyInternalMarkdownDirectiveHandler(metaclass=abc.ABCMeta):
|
||||
line, *_ = self._encode_line(trans, line)
|
||||
return self.handle_error(container, line, str(e))
|
||||
|
||||
export_markdown = _remap_galaxy_markdown_calls(_remap_container, internal_galaxy_markdown)
|
||||
return export_markdown
|
||||
def _remap_embed_container(match):
|
||||
container = match.group("container")
|
||||
object_id: Optional[int] = None
|
||||
encoded_id: Optional[str] = None
|
||||
|
||||
if id_match := re.search(UNENCODED_ID_PATTERN, match.group()):
|
||||
object_id = int(id_match.group(2))
|
||||
encoded_id = trans.security.encode_id(object_id)
|
||||
if container == "history_dataset_type":
|
||||
_check_object(object_id, match.group(0))
|
||||
hda = hda_manager.get_accessible(object_id, trans.user)
|
||||
return hda.extension or "data"
|
||||
elif container == "history_dataset_name":
|
||||
_check_object(object_id, match.group(0))
|
||||
hda = hda_manager.get_accessible(object_id, trans.user)
|
||||
return hda.name or ""
|
||||
elif container == "workflow_license":
|
||||
_check_object(object_id, match.group(0))
|
||||
stored_workflow = workflow_manager.get_stored_accessible_workflow(trans, encoded_id)
|
||||
return _workflow_license_as_simple_markdown(stored_workflow)
|
||||
elif container == "invocation_time":
|
||||
_check_object(object_id, match.group(0))
|
||||
invocation = workflow_manager.get_invocation(trans, object_id)
|
||||
return _database_time_to_str(invocation.create_time)
|
||||
elif container == "generate_time":
|
||||
return now().isoformat()
|
||||
elif container == "generate_galaxy_version":
|
||||
version = trans.app.config.version_major
|
||||
return version
|
||||
elif container == "instance_access_link":
|
||||
url = trans.app.config.instance_access_url
|
||||
return _link_to_markdown(url)
|
||||
elif container == "instance_resources_link":
|
||||
url = trans.app.config.instance_resource_url
|
||||
return _link_to_markdown(url)
|
||||
elif container == "instance_help_link":
|
||||
url = trans.app.config.helpsite_url
|
||||
return _link_to_markdown(url)
|
||||
elif container == "instance_support_link":
|
||||
url = trans.app.config.support_url
|
||||
return _link_to_markdown(url)
|
||||
elif container == "instance_citation_link":
|
||||
url = trans.app.config.citation_url
|
||||
return _link_to_markdown(url)
|
||||
elif container == "instance_terms_link":
|
||||
url = trans.app.config.terms_url
|
||||
return _link_to_markdown(url)
|
||||
elif container == "instance_organization_link":
|
||||
title = trans.app.config.organization_name
|
||||
url = trans.app.config.organization_url
|
||||
return _link_to_markdown(url, title)
|
||||
else:
|
||||
raise MalformedContents(f"Unknown embedded Galaxy Markdown directive encountered [{container}].")
|
||||
|
||||
export_markdown_raw_embed = _remap_galaxy_markdown_calls(_remap_container, internal_galaxy_markdown)
|
||||
|
||||
def _remap_embed_container_ids(match):
|
||||
object_id: Optional[str] = None
|
||||
|
||||
whole_match = match.group()
|
||||
if id_match := re.search(UNENCODED_ID_PATTERN, whole_match):
|
||||
object_id = id_match.group(2)
|
||||
encoded_id = trans.security.encode_id(object_id)
|
||||
whole_match = whole_match.replace(id_match.group(), f"{id_match.group(1)}={encoded_id}")
|
||||
|
||||
return whole_match
|
||||
|
||||
export_markdown = _remap_galaxy_markdown_embedded_containers(
|
||||
_remap_embed_container_ids, export_markdown_raw_embed
|
||||
)
|
||||
export_markdown_embed_expanded = _remap_galaxy_markdown_embedded_containers(
|
||||
_remap_embed_container, export_markdown_raw_embed
|
||||
)
|
||||
return export_markdown, export_markdown_embed_expanded
|
||||
|
||||
def _encode_line(self, trans, line):
|
||||
object_type = None
|
||||
@@ -524,8 +610,8 @@ class ReadyForExportMarkdownDirectiveHandler(GalaxyInternalMarkdownDirectiveHand
|
||||
pass
|
||||
|
||||
def handle_invocation_time(self, line, invocation):
|
||||
self.ensure_rendering_data_for("invocations", invocation)["create_time"] = invocation.create_time.strftime(
|
||||
"%Y-%m-%d, %H:%M:%S"
|
||||
self.ensure_rendering_data_for("invocations", invocation)["create_time"] = _database_time_to_str(
|
||||
invocation.create_time
|
||||
)
|
||||
|
||||
def handle_invocation_inputs(self, line, invocation):
|
||||
@@ -572,9 +658,9 @@ def ready_galaxy_markdown_for_export(trans, internal_galaxy_markdown):
|
||||
# Walk Galaxy directives inside the Galaxy Markdown and collect dict-ified data
|
||||
# needed to render this efficiently.
|
||||
directive_handler = ReadyForExportMarkdownDirectiveHandler(trans, extra_rendering_data)
|
||||
export_markdown = directive_handler.walk(trans, internal_galaxy_markdown)
|
||||
export_markdown, export_markdown_embed_expanded = directive_handler.walk(trans, internal_galaxy_markdown)
|
||||
export_markdown = process_invocation_ids(lambda value: trans.security.encode_id(int(value)), export_markdown)
|
||||
return export_markdown, extra_rendering_data
|
||||
return export_markdown, export_markdown_embed_expanded, extra_rendering_data
|
||||
|
||||
|
||||
class ToBasicMarkdownDirectiveHandler(GalaxyInternalMarkdownDirectiveHandler):
|
||||
@@ -674,15 +760,7 @@ class ToBasicMarkdownDirectiveHandler(GalaxyInternalMarkdownDirectiveHandler):
|
||||
return (markdown, True)
|
||||
|
||||
def handle_workflow_license(self, line, stored_workflow):
|
||||
# workflow_manager = self.trans.app.workflow_manager
|
||||
license_manager = LicensesManager()
|
||||
markdown = "*No license specified.*"
|
||||
if license_id := stored_workflow.latest_workflow.license:
|
||||
try:
|
||||
license_metadata = license_manager.get_license_by_id(license_id)
|
||||
markdown = f"[{license_metadata.name}]({license_metadata.url})"
|
||||
except ObjectNotFound:
|
||||
markdown = f"Unknown license ({license_id})"
|
||||
markdown = _workflow_license_as_simple_markdown(stored_workflow)
|
||||
return (f"\n\n{markdown}\n\n", True)
|
||||
|
||||
def handle_workflow_image(self, line, stored_workflow, workflow_version: Optional[int]):
|
||||
@@ -792,15 +870,11 @@ class ToBasicMarkdownDirectiveHandler(GalaxyInternalMarkdownDirectiveHandler):
|
||||
return self._handle_link(url, title)
|
||||
|
||||
def _handle_link(self, url, title=None):
|
||||
if not url:
|
||||
content = "*Not configured, please contact Galaxy admin*"
|
||||
return (content, True)
|
||||
elif not title:
|
||||
title = url
|
||||
return (f"[{title}]({url})", True)
|
||||
content = _link_to_markdown(url, title)
|
||||
return (content, True)
|
||||
|
||||
def handle_invocation_time(self, line, invocation):
|
||||
content = literal_via_fence(invocation.create_time.strftime("%Y-%m-%d, %H:%M:%S"))
|
||||
content = literal_via_fence(_database_time_to_str(invocation.create_time))
|
||||
return (content, True)
|
||||
|
||||
def handle_invocation_inputs(self, line, invocation):
|
||||
@@ -834,7 +908,7 @@ def to_basic_markdown(trans, internal_galaxy_markdown: str) -> str:
|
||||
"""Replace Galaxy Markdown extensions with plain Markdown for PDF/HTML export."""
|
||||
directive_handler = ToBasicMarkdownDirectiveHandler(trans)
|
||||
resolved_invocations_markdown = resolve_invocation_markdown(trans, internal_galaxy_markdown)
|
||||
plain_markdown = directive_handler.walk(trans, resolved_invocations_markdown)
|
||||
_, plain_markdown = directive_handler.walk(trans, resolved_invocations_markdown)
|
||||
return plain_markdown
|
||||
|
||||
|
||||
@@ -1167,6 +1241,36 @@ def resolve_job_markdown(trans, job, job_markdown):
|
||||
return galaxy_markdown
|
||||
|
||||
|
||||
def _workflow_license_as_simple_markdown(stored_workflow):
|
||||
license_manager = LicensesManager()
|
||||
markdown = "*No license specified.*"
|
||||
if license_id := stored_workflow.latest_workflow.license:
|
||||
try:
|
||||
license_metadata = license_manager.get_license_by_id(license_id)
|
||||
markdown = f"[{license_metadata.name}]({license_metadata.url})"
|
||||
except ObjectNotFound:
|
||||
markdown = f"Unknown license ({license_id})"
|
||||
return markdown
|
||||
|
||||
|
||||
def _check_object(object_id: Optional[int], line: str) -> None:
|
||||
if object_id is None:
|
||||
raise MalformedContents(f"Missing object identifier [{line}].")
|
||||
|
||||
|
||||
def _database_time_to_str(database_time: datetime) -> str:
|
||||
return database_time.strftime("%Y-%m-%d, %H:%M:%S")
|
||||
|
||||
|
||||
def _link_to_markdown(url: Optional[str], title: Optional[str] = None):
|
||||
if not url:
|
||||
content = "*Link not configured, please contact Galaxy admin*"
|
||||
return content
|
||||
elif not title:
|
||||
title = url
|
||||
return f"[{title}]({url})"
|
||||
|
||||
|
||||
def _remap_galaxy_markdown_containers(func, markdown):
|
||||
new_markdown = markdown
|
||||
|
||||
@@ -1193,6 +1297,28 @@ def _remap_galaxy_markdown_containers(func, markdown):
|
||||
return new_markdown
|
||||
|
||||
|
||||
def _remap_galaxy_markdown_embedded_containers(func, markdown):
|
||||
new_markdown = markdown
|
||||
|
||||
searching_from = 0
|
||||
while True:
|
||||
from_markdown = new_markdown[searching_from:]
|
||||
match = re.search(EMBED_DIRECTIVE_REGEX, from_markdown)
|
||||
if match is not None:
|
||||
replacement = func(match)
|
||||
start_pos = match.start()
|
||||
end_pos = match.end()
|
||||
|
||||
start_pos = start_pos + searching_from
|
||||
end_pos = end_pos + searching_from
|
||||
new_markdown = new_markdown[:start_pos] + replacement + new_markdown[end_pos:]
|
||||
searching_from = start_pos + len(replacement)
|
||||
else:
|
||||
break
|
||||
|
||||
return new_markdown
|
||||
|
||||
|
||||
def _parse_directive_argument_value(arg_name: str, line: str) -> Optional[str]:
|
||||
arg_pattern = re.compile(rf"{arg_name}=\s*{ARG_VAL_CAPTURED_REGEX}\s*")
|
||||
match = re.search(arg_pattern, line)
|
||||
|
||||
@@ -374,8 +374,9 @@ class PageManager(sharable.SharableModelManager[model.Page], UsesAnnotations):
|
||||
content = unicodify(processor.output(), "utf-8")
|
||||
as_dict["content"] = content
|
||||
elif content_format == PageContentFormat.markdown.value:
|
||||
content, extra_attributes = ready_galaxy_markdown_for_export(trans, content)
|
||||
as_dict["content"] = content
|
||||
content, content_embed_expanded, extra_attributes = ready_galaxy_markdown_for_export(trans, content)
|
||||
as_dict["content"] = content_embed_expanded
|
||||
as_dict["content_editor"] = content
|
||||
as_dict.update(extra_attributes)
|
||||
else:
|
||||
raise exceptions.RequestParameterInvalidException(
|
||||
|
||||
@@ -3815,6 +3815,12 @@ ContentFormatField: PageContentFormat = Field(
|
||||
ContentField: Optional[str] = Field(
|
||||
default="",
|
||||
title="Content",
|
||||
description="Text contents of the last page revision with embedded directives expanded (type dependent on content_format).",
|
||||
)
|
||||
|
||||
ContentEditorField: Optional[str] = Field(
|
||||
default="",
|
||||
title="Content for Editor",
|
||||
description="Raw text contents of the last page revision (type dependent on content_format).",
|
||||
)
|
||||
|
||||
@@ -4030,6 +4036,7 @@ class PageDetails(PageSummary):
|
||||
annotation: Optional[str] = AnnotationField
|
||||
content_format: PageContentFormat = ContentFormatField
|
||||
content: Optional[str] = ContentField
|
||||
content_editor: Optional[str] = ContentEditorField
|
||||
generate_version: Optional[str] = GenerateVersionField
|
||||
generate_time: Optional[str] = GenerateTimeField
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
@@ -43,12 +43,14 @@ class WorkflowMarkdownGeneratorPlugin(WorkflowReportGeneratorPlugin, metaclass=A
|
||||
internal_markdown = self._generate_internal_markdown(
|
||||
trans, invocation, runtime_report_config_json=runtime_report_config_json
|
||||
)
|
||||
export_markdown, extra_rendering_data = ready_galaxy_markdown_for_export(trans, internal_markdown)
|
||||
export_markdown, export_markdown_embed_expanded, extra_rendering_data = ready_galaxy_markdown_for_export(
|
||||
trans, internal_markdown
|
||||
)
|
||||
# Invocations can only be run on history, and user must exist, so this should always work
|
||||
username = invocation.history and invocation.history.user and invocation.history.user.username
|
||||
rval = {
|
||||
"render_format": "markdown", # Presumably the frontend could render things other ways.
|
||||
"markdown": export_markdown,
|
||||
"markdown": export_markdown_embed_expanded,
|
||||
"invocation_markdown": export_markdown,
|
||||
"model_class": "Report",
|
||||
"id": trans.app.security.encode_id(invocation.workflow_id),
|
||||
|
||||
@@ -339,7 +339,7 @@ history_dataset_display(history_dataset_id=1)
|
||||
```
|
||||
"""
|
||||
with self._expect_get_hda(hda):
|
||||
export_markdown, extra_data = self._ready_export(example)
|
||||
_, export_markdown, extra_data = self._ready_export(example)
|
||||
assert "history_datasets" in extra_data
|
||||
assert len(extra_data["history_datasets"]) == 1
|
||||
|
||||
@@ -357,7 +357,7 @@ history_dataset_display(history_dataset_id=2)
|
||||
```
|
||||
"""
|
||||
self.app.hda_manager.get_accessible.side_effect = [hda, hda2]
|
||||
export_markdown, extra_data = self._ready_export(example)
|
||||
_, export_markdown, extra_data = self._ready_export(example)
|
||||
assert "history_datasets" in extra_data
|
||||
assert len(extra_data["history_datasets"]) == 2
|
||||
|
||||
@@ -384,7 +384,7 @@ history_dataset_collection_display(history_dataset_collection_id=1)
|
||||
mock.patch.object(HDCASerializer, "url_for", return_value="http://google.com"),
|
||||
mock.patch.object(HDCASerializer, "serialize_to_view", return_value=mock_hdca_view),
|
||||
):
|
||||
export, extra_data = self._ready_export(example)
|
||||
_, export, extra_data = self._ready_export(example)
|
||||
assert "history_dataset_collections" in extra_data
|
||||
assert len(extra_data.get("history_dataset_collections")) == 1
|
||||
|
||||
@@ -394,7 +394,7 @@ history_dataset_collection_display(history_dataset_collection_id=1)
|
||||
generate_galaxy_version()
|
||||
```
|
||||
"""
|
||||
result, extra_data = self._ready_export(example)
|
||||
_, result, extra_data = self._ready_export(example)
|
||||
assert "generate_version" in extra_data
|
||||
assert extra_data["generate_version"] == "19.09"
|
||||
|
||||
@@ -404,7 +404,7 @@ generate_galaxy_version()
|
||||
generate_time()
|
||||
```
|
||||
"""
|
||||
result, extra_data = self._ready_export(example)
|
||||
_, result, extra_data = self._ready_export(example)
|
||||
assert "generate_time" in extra_data
|
||||
|
||||
def test_get_invocation_time(self):
|
||||
@@ -415,12 +415,95 @@ generate_time()
|
||||
invocation_time(invocation_id=1)
|
||||
```
|
||||
"""
|
||||
result, extra_data = self._ready_export(example)
|
||||
_, result, extra_data = self._ready_export(example)
|
||||
assert "invocations" in extra_data
|
||||
assert "create_time" in extra_data["invocations"]["be8be0fd2ce547f6"]
|
||||
assert extra_data["invocations"]["be8be0fd2ce547f6"]["create_time"] == invocation.create_time.strftime(
|
||||
"%Y-%m-%d, %H:%M:%S"
|
||||
)
|
||||
|
||||
def _ready_export(self, example):
|
||||
def test_export_replaces_embedded_history_dataset_type(self):
|
||||
hda = self._new_hda()
|
||||
hda.extension = "fasta"
|
||||
hda2 = self._new_hda()
|
||||
hda2.extension = "fastqsanger"
|
||||
hda2.id = 2
|
||||
example = """
|
||||
I ran a cool analysis with two inputs of types ${galaxy history_dataset_type(history_dataset_id=1)} and ${galaxy history_dataset_type(history_dataset_id=2)}.
|
||||
"""
|
||||
self.app.hda_manager.get_accessible.side_effect = [hda, hda2]
|
||||
_, export_markdown, _ = self._ready_export(example)
|
||||
assert (
|
||||
export_markdown
|
||||
== """
|
||||
I ran a cool analysis with two inputs of types fasta and fastqsanger.
|
||||
"""
|
||||
)
|
||||
|
||||
def test_export_replaces_embedded_history_dataset_name(self):
|
||||
hda = self._new_hda()
|
||||
hda.name = "foo bar"
|
||||
hda2 = self._new_hda()
|
||||
hda2.name = "cow dog"
|
||||
hda2.id = 2
|
||||
example = """
|
||||
I ran a cool analysis with two inputs of types ${galaxy history_dataset_name(history_dataset_id=1)} and ${galaxy history_dataset_name(history_dataset_id=2)}.
|
||||
"""
|
||||
self.app.hda_manager.get_accessible.side_effect = [hda, hda2]
|
||||
_, export_markdown, _ = self._ready_export(example)
|
||||
assert (
|
||||
export_markdown
|
||||
== """
|
||||
I ran a cool analysis with two inputs of types foo bar and cow dog.
|
||||
"""
|
||||
)
|
||||
|
||||
def test_export_replaces_embedded_generate_time(self):
|
||||
example = """
|
||||
I ran a cool analysis at ${galaxy generate_time()}.
|
||||
"""
|
||||
_, export_markdown, _ = self._ready_export(example)
|
||||
assert export_markdown.startswith(
|
||||
"""
|
||||
I ran a cool analysis at 2"""
|
||||
)
|
||||
|
||||
def test_export_replaces_embedded_invocation_time(self):
|
||||
invocation = self._new_invocation()
|
||||
self.app.workflow_manager.get_invocation.side_effect = [invocation]
|
||||
example = """
|
||||
I ran a cool analysis at ${galaxy invocation_time(invocation_id=1)}.
|
||||
"""
|
||||
_, export_markdown, _ = self._ready_export(example)
|
||||
assert export_markdown.startswith(
|
||||
"""
|
||||
I ran a cool analysis at 2"""
|
||||
)
|
||||
|
||||
def test_export_replaces_embedded_galaxy_version(self):
|
||||
example = """
|
||||
I ran a cool analysis with Galaxy ${galaxy generate_galaxy_version()}.
|
||||
"""
|
||||
_, export_markdown, _ = self._ready_export(example)
|
||||
assert (
|
||||
export_markdown
|
||||
== """
|
||||
I ran a cool analysis with Galaxy 19.09.
|
||||
"""
|
||||
)
|
||||
|
||||
def test_export_replaces_embedded_access_link(self):
|
||||
self.trans.app.config.instance_access_url = "http://mycoolgalaxy.org"
|
||||
example = """
|
||||
I ran a cool analysis at ${galaxy instance_access_link()}.
|
||||
"""
|
||||
_, export_markdown, _ = self._ready_export(example)
|
||||
assert (
|
||||
export_markdown
|
||||
== """
|
||||
I ran a cool analysis at [http://mycoolgalaxy.org](http://mycoolgalaxy.org).
|
||||
"""
|
||||
)
|
||||
|
||||
def _ready_export(self, example: str):
|
||||
return ready_galaxy_markdown_for_export(self.trans, example)
|
||||
|
||||
@@ -304,3 +304,45 @@ visualization(foo|bar=hello)
|
||||
```
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def test_markdown_validation_embed():
|
||||
assert_markdown_valid(
|
||||
"""
|
||||
| moo | cow |
|
||||
| 1 | 2 |
|
||||
"""
|
||||
)
|
||||
assert_markdown_valid(
|
||||
"""
|
||||
| moo | cow |
|
||||
| 1 | ${galaxy generate_galaxy_version()} |
|
||||
"""
|
||||
)
|
||||
assert_markdown_valid(
|
||||
"""
|
||||
| moo | cow |
|
||||
| 1 | ${galaxy history_dataset_name(input=foobar)} |
|
||||
"""
|
||||
)
|
||||
assert_markdown_invalid(
|
||||
"""
|
||||
| moo | cow |
|
||||
| 1 | ${galaxy history_dataset_name(foo=bar)} |
|
||||
""",
|
||||
at_line=2,
|
||||
)
|
||||
assert_markdown_invalid(
|
||||
"""
|
||||
| moo | cow |
|
||||
| 1 | ${galaxy generate_galaxy_version(moo=cow)} |
|
||||
""",
|
||||
at_line=2,
|
||||
)
|
||||
assert_markdown_invalid(
|
||||
"""
|
||||
| moo | cow |
|
||||
| 1 | ${galaxy invalid()} |
|
||||
""",
|
||||
at_line=2,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user