Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions client/src/components/ActivityBar/ActivitySettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ function executeActivity(activity: Activity) {
v-for="activity in filteredActivities"
:key="activity.id"
class="activity-settings-item p-2 cursor-pointer"
:data-activity-id="activity.id"
:data-activity-visible="!!activity.visible"
@click="executeActivity(activity)">
<div class="d-flex justify-content-between align-items-start">
<span class="d-flex justify-content-between w-100">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ defineExpose({

<template>
<label style="display: none">
<input ref="fileInputRef" type="file" accept=".xlsx" @change="onFileUpload" />
<input
ref="fileInputRef"
data-description="workbook-file-input"
type="file"
accept=".xlsx"
@change="onFileUpload" />
</label>
</template>
2 changes: 2 additions & 0 deletions client/src/components/Collections/wizard/fetchWorkbooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ const COLUMN_TITLE_PREFIXES: Record<string, ColumnMappingType> = {
deferredurl: "url_deferred",
genome: "dbkey",
dbkey: "dbkey",
build: "dbkey",
filetype: "file_type",
type: "file_type",
extension: "file_type",
info: "info",
tag: "tags",
Expand Down
13 changes: 13 additions & 0 deletions client/src/utils/navigation/navigation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ dataset_display:
container: .dataset-display
content: body

activity_bar:
selectors:
settings: "#activity-settings"
show_activity: "button[data-activity-id='${activity_id}'] button"
additional_activities: '[data-description="Additional Activities"]'

history_panel:
menu:
labels:
Expand Down Expand Up @@ -1380,6 +1386,13 @@ rule_builder:
table: '#rules-ag-grid .ag-root'
extension_select: '.rule-footer-extension-group .extension-select'

activity: '#activity-rules'
workbook_upload_button: '.workbook-upload-helper'
workbook_upload_file_input: '[data-description="workbook-file-input"]'
create_collections: '[data-creating-what="collections"]'
wizard_import: ".wizard-actions .btn-primary"


charts:
selectors:
visualize_button: '.ui-portlet .button i.fa-line-chart' # without icon - it waits on other buttons that aren't visible, need more specific class
Expand Down
2 changes: 1 addition & 1 deletion lib/galaxy/managers/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@

from galaxy.managers import base
from galaxy.managers.context import ProvidesUserContext
from galaxy.managers.markdown_util import weasyprint_available
from galaxy.schema import SerializationParams
from galaxy.structured_app import StructuredApp
from galaxy.util.markdown import weasyprint_available

log = logging.getLogger(__name__)

Expand Down
49 changes: 5 additions & 44 deletions lib/galaxy/managers/markdown_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,13 @@
import logging
import os
import re
import shutil
import tempfile
from datetime import datetime
from re import Match
from typing import (
Any,
Optional,
)

import markdown

try:
import weasyprint
except Exception:
weasyprint = None

from galaxy.config import GalaxyAppConfiguration
from galaxy.exceptions import (
MalformedContents,
Expand All @@ -54,9 +45,11 @@
ShortTermStorageMonitor,
storage_context,
)
from galaxy.util.markdown import literal_via_fence
from galaxy.util.resources import resource_string
from galaxy.util.sanitize_html import sanitize_html
from galaxy.util.markdown import (
literal_via_fence,
to_pdf_raw,
weasyprint_available,
)
from .markdown_parse import (
EMBED_DIRECTIVE_REGEX,
GALAXY_MARKDOWN_FUNCTION_CALL_LINE,
Expand Down Expand Up @@ -915,38 +908,6 @@ def to_basic_markdown(trans, internal_galaxy_markdown: str) -> str:
return plain_markdown


def to_html(basic_markdown: str) -> str:
# Allow data: urls so we can embed images.
html = sanitize_html(markdown.markdown(basic_markdown, extensions=["tables"]), allow_data_urls=True)
return html


def to_pdf_raw(basic_markdown: str, css_paths: Optional[list[str]] = None) -> bytes:
"""Convert RAW markdown with specified CSS paths into bytes of a PDF."""
css_paths = css_paths or []
as_html = to_html(basic_markdown)
directory = tempfile.mkdtemp("gxmarkdown")
index = os.path.join(directory, "index.html")
try:
output_file = open(index, "w", encoding="utf-8", errors="xmlcharrefreplace")
output_file.write(as_html)
output_file.close()
html = weasyprint.HTML(filename=index)
stylesheets = [weasyprint.CSS(string=resource_string(__name__, "markdown_export_base.css"))]
for css_path in css_paths:
with open(css_path) as f:
css_content = f.read()
css = weasyprint.CSS(string=css_content)
stylesheets.append(css)
return html.write_pdf(stylesheets=stylesheets)
finally:
shutil.rmtree(directory)


def weasyprint_available() -> bool:
return weasyprint is not None


def _check_can_convert_to_pdf_or_raise():
"""Checks if the HTML to PDF converter is available."""
if not weasyprint_available():
Expand Down
2 changes: 1 addition & 1 deletion lib/galaxy/managers/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
ConfigDoesNotAllowException,
ObjectNotFound,
)
from galaxy.managers.markdown_util import to_html
from galaxy.model import (
GroupRoleAssociation,
Notification,
Expand Down Expand Up @@ -64,6 +63,7 @@
UserNotificationPreferences,
UserNotificationUpdateRequest,
)
from galaxy.util.markdown import to_html

log = logging.getLogger(__name__)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,20 @@
- doc: "dbkey maps to the dbkey target type"
column_header: "dbkey"
maps_to: "dbkey"
- doc: "build maps to the dbkey target type"
column_header: "build"
maps_to: "dbkey"


- doc: "filetype maps to the file_type target type"
column_header: "file type"
maps_to: "file_type"
- doc: "extension maps to the file_type target type"
column_header: "extension"
maps_to: "file_type"
- doc: "Type maps to the file_type target type"
column_header: "Type"
maps_to: "file_type"

- doc: "info maps to the info target type"
column_header: "info"
Expand Down
2 changes: 2 additions & 0 deletions lib/galaxy/model/dataset_collections/rule_target_columns.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
"genome": "dbkey",
"dbkey": "dbkey",
"genomebuild": "dbkey",
"build": "dbkey",
"filetype": "file_type",
"extension": "file_type",
"fileextension": "file_type",
"type": "file_type",
"info": "info",
"tag": "tags",
"grouptag": "group_tags",
Expand Down
83 changes: 73 additions & 10 deletions lib/galaxy/selenium/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,24 @@
HEADLESS_DESCRIPTION = "Use local selenium headlessly (native in chrome, otherwise this requires pyvirtualdisplay)."
BACKEND_DESCRIPTION = "Browser automation backend to use (selenium or playwright)."

from typing import Literal
import os
from typing import (
Literal,
Optional,
)
from urllib.parse import urljoin

from .driver_factory import (
ConfiguredDriver,
virtual_display_if_enabled,
)
from .navigates_galaxy import (
galaxy_timeout_handler,
NavigatesGalaxy,
from .navigates_galaxy import galaxy_timeout_handler
from .stories import (
NoopStory,
Story,
StoryProtocol,
)
from .stories.data.upload import UploadStoriesMixin


def add_selenium_arguments(parser):
Expand Down Expand Up @@ -60,11 +67,41 @@ def add_selenium_arguments(parser):
choices=["selenium", "playwright"],
help=BACKEND_DESCRIPTION,
)
parser.add_argument(
"--timeout-multiplier",
type=float,
default=1.0,
help="Multiplier to apply to all timeout values (for slower environments).",
)
return parser


def add_story_arguments(parser):
"""Add story generation arguments for CLI scripts.

All the browser automation arguments (add_selenium_arguments) are also added.
"""
add_selenium_arguments(parser)
parser.add_argument(
"--story-output",
default=None,
help="Directory to generate story documentation (markdown, HTML, PDF)",
)
parser.add_argument(
"--story-title",
default=None,
help="Title for the generated story",
)
parser.add_argument(
"--story-description",
default=None,
help="Description for the generated story",
)

return parser


class DriverWrapper(NavigatesGalaxy):
class DriverWrapper(UploadStoriesMixin):
"""Adapt argparse command-line options to a browser automation driver."""

def __init__(self, args):
Expand All @@ -81,9 +118,8 @@ def __init__(self, args):
self.display = virtual_display_if_enabled(args.selenium_headless)

# Create configured driver with the specified backend
# TODO: parameterize timeout multiplier
self.configured_driver = ConfiguredDriver(
galaxy_timeout_handler(1.0),
galaxy_timeout_handler(args.timeout_multiplier),
browser=browser,
remote=args.selenium_remote,
remote_host=args.selenium_remote_host,
Expand All @@ -93,6 +129,18 @@ def __init__(self, args):
)
self.target_url = args.galaxy_url

# Initialize story (real or noop)
# Scripts opt-in by calling add_story_arguments()
if hasattr(args, "story_output") and args.story_output:
if not os.path.exists(args.story_output):
os.makedirs(args.story_output)

title = args.story_title or "Galaxy Tutorial"
description = args.story_description or ""
self.story: StoryProtocol = Story(title, description, args.story_output)
else:
self.story = NoopStory()

@property
def _driver_impl(self):
"""Provide driver implementation from configured_driver."""
Expand All @@ -101,11 +149,23 @@ def _driver_impl(self):
def build_url(self, url="", for_selenium: bool = True):
return urljoin(self.target_url, url)

def screenshot(self, label: str) -> None:
"""No-op in this context, not saving debugging/testing screenshots.
def screenshot(self, label: str, caption: Optional[str] = None) -> None:
"""Save screenshot if story tracking is enabled.

Consider a verbose or debug option for saving these.
Args:
label: Base filename for screenshot
caption: Optional caption for the screenshot in the story
"""
if self.story.enabled:
# Generate screenshot path with sequential numbering
target = os.path.join(self.story.output_directory, f"{self.story.screenshot_counter:03d}_{label}.png")
self.story.screenshot_counter += 1

# Save screenshot
self.save_screenshot(target)

# Add to story
self.story.add_screenshot(target, caption or label)

@property
def default_timeout(self):
Expand All @@ -130,3 +190,6 @@ def finish(self):

if exception is not None:
raise exception

def _screenshot_path(self, label: str, extension: str = ".png") -> Optional[str]:
return None
Loading
Loading