Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
adc5da0
Implement a workflow for AI-assisted Selenium test case generation.
jmchilton Oct 10, 2025
a6892c5
Bug fix for recent browser automation timeout changes.
jmchilton Oct 27, 2025
68dc7b3
Implement Test Stories feature for visual test documentation
jmchilton Oct 27, 2025
a0efc15
Update AI documentation to reflect test stories feature
jmchilton Oct 27, 2025
f0fe724
Selenium test cases and fixes for workbook uploads.
jmchilton Nov 7, 2025
098014c
Refactor markdown utilities to galaxy.util for cleaner dependencies
jmchilton Oct 28, 2025
a61806a
Reorganize story module into stories package with data helpers
jmchilton Oct 28, 2025
97c8f93
Add document_file() method for documenting file contents in test stories
jmchilton Oct 28, 2025
9f6b8b0
Update test case to use new document_file story helper.
jmchilton Oct 28, 2025
f50020c
Generate 'latest' story symlink the way we do for test errors.
jmchilton Oct 29, 2025
794956c
Refactor NavigatesGalaxyMixin into support package.
jmchilton Oct 29, 2025
a47a2f7
Split out tutorials examples for reuse outside tests.
jmchilton Oct 29, 2025
88e2f6e
Revise language for generated tutorials.
jmchilton Oct 29, 2025
f27b650
Refactor existing rule builder test examples into reusable stories.
jmchilton Oct 29, 2025
05325d9
Revise constants...
jmchilton Oct 29, 2025
7159fd0
Fill in documentation for older upload stories.
jmchilton Oct 29, 2025
f0f6195
Replace story CLI example with working rule builder tutorial generator.
jmchilton Oct 29, 2025
8a08a54
Add highlight_element operation for visual debugging
jmchilton Oct 30, 2025
1561470
Docstrings for rule mapping stuff so Claude can generate better docs.
jmchilton Oct 30, 2025
d52368c
More comprehensive docs & tests for workbook uploads.
jmchilton Oct 30, 2025
217939b
Implement embedded document fragments into browser automation stories.
jmchilton Oct 30, 2025
af5797d
Rebuild schema for docstrings.
jmchilton Nov 7, 2025
756bea0
Fix up docs.
jmchilton Nov 7, 2025
a3e4935
Bug fix.
jmchilton Nov 9, 2025
6c7516e
Runtime fixes for stories.
jmchilton Nov 9, 2025
05d61d8
Implement composable, rerunnable story sections.
jmchilton Nov 17, 2025
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
21 changes: 19 additions & 2 deletions client/src/api/schema/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14887,7 +14887,19 @@ export interface components {
/** Message */
message: string;
};
/** InferredColumnMapping */
/**
* InferredColumnMapping
* @description Records that a column at a specific index was successfully parsed and mapped.
*
* Used in parse logs to show users which columns were recognized and how they were interpreted.
*
* Example log entry:
* InferredColumnMapping(
* column_index=2,
* column_title="MD5 Sum",
* parsed_column=ParsedColumn(type="hash_md5", type_index=0, title="MD5 Sum")
* )
*/
InferredColumnMapping: {
/** Column Index */
column_index: number;
Expand Down Expand Up @@ -18660,7 +18672,12 @@ export interface components {
*/
content: string;
};
/** ParsedColumn */
/**
* ParsedColumn
* @description Serializable representation of a parsed column (Pydantic model version of HeaderColumn).
*
* Used in API responses and logging to communicate which columns were recognized.
*/
ParsedColumn: {
/** Title */
title: string;
Expand Down
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 @@ -21,7 +21,7 @@ const emit = defineEmits(["download"]);
<BCardTitle>
<b>Step 1: Download</b>
</BCardTitle>
<BLink :href="generateWorkbookLink" @click="emit('download')"
<BLink data-description="workbook download link" :href="generateWorkbookLink" @click="emit('download')"
><FontAwesomeIcon size="xl" :icon="faDownload" /> Download workbook.</BLink
>
</BCard>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const emit = defineEmits(["workbookContents"]);
</script>
<template>
<BCard
data-description="workbook upload card"
data-galaxy-file-drop-target
:class="dropZoneClasses"
@drop.prevent="handleDrop"
Expand All @@ -38,7 +39,7 @@ const emit = defineEmits(["workbookContents"]);
</BAlert>
</div>
<div>
<BLink href="#" @click.prevent="browseFiles">
<BLink data-description="workbook upload link" href="#" @click.prevent="browseFiles">
<FontAwesomeIcon size="xl" :icon="faUpload" />
Drop completed workbook here or click to upload.
</BLink>
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,.xls,.tsv,.csv,.tabular" @change="onFileUpload" />
<input
ref="fileInputRef"
data-description="workbook-file-input"
type="file"
accept=".xlsx,.xls,.tsv,.csv,.tabular"
@change="onFileUpload" />
</label>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import { BCardGroup } from "bootstrap-vue";
import { computed } from "vue";

import { withPrefix } from "@/utils/redirect";

import type { ParsedFetchWorkbookForCollectionCollectionType, RulesCreatingWhat } from "./types";

import CardDownloadWorkbook from "./CardDownloadWorkbook.vue";
Expand All @@ -25,7 +27,7 @@ const emit = defineEmits(["workbookContents"]);
const generateWorkbookHref = computed(() => {
let type;
if (props.creatingWhat === "datasets") {
type = "dataset";
type = "datasets";
} else if (props.includeCollectionName) {
type = "collections";
} else {
Expand All @@ -36,7 +38,7 @@ const generateWorkbookHref = computed(() => {
if (type === "collection" || type === "collections") {
url = `${url}&collection_type=${props.collectionType}`;
}
return url;
return withPrefix(url);
});
</script>

Expand Down
17 changes: 17 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 @@ -1386,6 +1392,17 @@ 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"
wizard_next: ".wizard-actions .go-next-btn"
data_import_source_from: '[data-import-source-from="${source}"]'
workbook_download_link: '[data-description="workbook download link"]'
workbook_upload_link: '[data-description="workbook upload card"] [data-description="workbook upload link"]'
workbook_upload_file_input_explicit: '[data-description="workbook upload card"] [data-description="workbook-file-input"]'

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
@@ -1,8 +1,32 @@
# The idea that we reserve some column names for reserved purposes in Galaxy
# is going to span fetch workbooks and sample sheet workbooks - and we need
# to validate it both on the backend and the frontend. So having a set of tests
# that validate what are reserved column names that we can run across both languages
# utilities for doing this is valuable.
# Test Specification: Column Header to Target Type Mappings
#
# This file defines test cases for Galaxy's flexible column header parsing system.
# Each test case specifies:
# - doc: Human-readable description
# - column_header: The user-provided column header (as it appears in workbook)
# - maps_to: The expected internal target type key
#
# Purpose:
# Galaxy needs to recognize column headers in workbooks (Excel/CSV) across different
# naming conventions and formats. This specification serves as:
# 1. Test cases for backend Python parsing (column_title_to_target_type)
# 2. Test cases for frontend TypeScript parsing (when implemented)
# 3. Documentation of supported column header variations
#
# The parsing is case-insensitive and ignores:
# - Whitespace
# - Special characters: ( ) - _
# - The word "optional"
#
# Examples of flexible parsing:
# "Name", "name", "NAME" -> "name"
# "MD5 Sum", "MD5", "Hash MD5", "md5sum" -> "hash_md5"
# "URI 1 (Forward)", "url", "URL" -> "url"
# "Genome Build", "DBKey", "genome" -> "dbkey"
#
# Test Execution:
# Python: test/unit/data/dataset_collections/test_column_to_target_mapping.py
# TypeScript: (future implementation)

- doc: "Name (with uppercase) maps directly to the name target type"
column_header: "Name"
Expand Down
Loading
Loading