Merge pull request #21477 from ahmedhamidawan/merge_25.1_into_dev_de

Merge 25.1 into dev
This commit is contained in:
Ahmed Hamid Awan
2025-12-17 11:08:30 +05:00
committed by GitHub
9 changed files with 158 additions and 15 deletions
+30 -1
View File
@@ -8,6 +8,7 @@ on:
- 'release_*'
pull_request_target:
types: [opened, reopened, synchronize]
workflow_dispatch:
permissions:
contents: read
@@ -22,7 +23,6 @@ jobs:
steps:
- uses: actions/checkout@v6
with:
ref: dev
sparse-checkout: |
lib/galaxy/version.py
@@ -35,7 +35,36 @@ jobs:
echo "version=${RELEASE_VERSION}" >> $GITHUB_OUTPUT
echo "Galaxy release version: ${RELEASE_VERSION}"
- name: Check if we should update draft
id: check-draft
run: |
VERSION="${{ inputs.version || steps.galaxy-version.outputs.version }}"
# Check if this version is already published
PUBLISHED=$(gh release view "v${VERSION}" --json isDraft --jq '.isDraft' 2>/dev/null || echo "not_found")
if [ "$PUBLISHED" = "false" ]; then
echo "Published release v${VERSION} already exists. Skipping."
echo "skip=true" >> $GITHUB_OUTPUT
exit 0
fi
# Check if a draft exists for a DIFFERENT version (only one draft at a time)
EXISTING_DRAFT=$(gh release list --limit 50 --json tagName,isDraft --jq '.[] | select(.isDraft == true) | .tagName' | head -1)
if [ -n "$EXISTING_DRAFT" ] && [ "$EXISTING_DRAFT" != "v${VERSION}" ]; then
echo "Draft release $EXISTING_DRAFT already exists (for different version). Skipping v${VERSION}."
echo "Only one draft release at a time is supported."
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "Proceeding with draft for v${VERSION}."
echo "skip=false" >> $GITHUB_OUTPUT
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: release-drafter/release-drafter@v6
if: steps.check-draft.outputs.skip == 'false'
with:
config-name: release-drafter.yml
version: ${{ steps.galaxy-version.outputs.version }}
+2
View File
@@ -137,6 +137,8 @@ class DatasetCollectionManager:
collection_type_description = structure.collection_type_description
dataset_collection = DatasetCollection(populated=False)
dataset_collection.collection_type = collection_type_description.collection_type
# Preserve column_definitions from input structure for sample sheets
dataset_collection.column_definitions = structure.column_definitions
elements = []
for index, (identifier, substructure) in enumerate(structure.children):
# TODO: Open question - populate these now or later?
@@ -64,12 +64,15 @@ class UninitializedTree(BaseTree):
class Tree(BaseTree):
children_known = True
def __init__(self, children, collection_type_description, when_values=None, columns_metadata=None):
def __init__(
self, children, collection_type_description, when_values=None, columns_metadata=None, column_definitions=None
):
super().__init__(collection_type_description)
self.children = children
self.when_values = when_values
# columns_metadata is a dict mapping element_identifier to columns data
self.columns_metadata = columns_metadata or {}
self.column_definitions = column_definitions
@staticmethod
def for_dataset_collection(dataset_collection, collection_type_description):
@@ -90,7 +93,12 @@ class Tree(BaseTree):
# Capture columns metadata from sample sheet collections
if element.columns is not None:
columns_metadata[element.element_identifier] = element.columns
return Tree(children, collection_type_description, columns_metadata=columns_metadata)
return Tree(
children,
collection_type_description,
columns_metadata=columns_metadata,
column_definitions=dataset_collection.column_definitions,
)
def walk_collections(self, hdca_dict):
return self._walk_collections(dict_map(lambda hdca: hdca.collection, hdca_dict))
@@ -148,12 +156,22 @@ class Tree(BaseTree):
for identifier, structure in self.children:
new_children.append((identifier, structure.multiply(other_structure)))
# Preserve columns_metadata when multiplying
return Tree(new_children, new_collection_type, columns_metadata=self.columns_metadata.copy())
# Preserve columns_metadata and column_definitions when multiplying
return Tree(
new_children,
new_collection_type,
columns_metadata=self.columns_metadata.copy(),
column_definitions=self.column_definitions,
)
def clone(self):
cloned_children = [(_[0], _[1].clone()) for _ in self.children]
return Tree(cloned_children, self.collection_type_description, columns_metadata=self.columns_metadata.copy())
return Tree(
cloned_children,
self.collection_type_description,
columns_metadata=self.columns_metadata.copy(),
column_definitions=self.column_definitions,
)
def __str__(self):
return f"Tree[collection_type={self.collection_type_description},children=({','.join(f'{identifier_and_element[0]}={identifier_and_element[1]}' for identifier_and_element in self.children)})]"
-7
View File
@@ -1029,13 +1029,6 @@ class InputModule(WorkflowModule):
step_outputs["input_ds_copy"] = new_hdca
else:
raise Exception("Unknown history content encountered")
# If coming from UI - we haven't registered invocation inputs yet,
# so do that now so dependent steps can be recalculated. In the future
# everything should come in from the API and this can be eliminated.
if not invocation.has_input_for_step(step.id):
content = next(iter(step_outputs.values()))
if content and content is not NO_REPLACEMENT:
invocation.add_input(content, step.id)
progress.set_outputs_for_input(invocation_step, step_outputs)
return None
+6
View File
@@ -623,6 +623,12 @@ class WorkflowProgress:
if step.label and step.type == "parameter_input" and "output" in outputs:
self.runtime_replacements[step.label] = str(outputs["output"])
invocation = invocation_step.workflow_invocation
if not invocation.has_input_for_step(step.id):
content = outputs.get("output", NO_REPLACEMENT)
if content is not NO_REPLACEMENT:
log.info("ADDING INPUT FOR STEP %s: %s", step.id, content, exc_info=True)
invocation.add_input(content, step.id)
self.set_step_outputs(invocation_step, outputs, already_persisted=already_persisted)
def effective_replacement_dict(self):
@@ -481,6 +481,7 @@ class TestDatasetCollectionsApi(ApiTestCase):
collection_details = self.dataset_populator.get_history_collection_details(
history_id, content_id=output_collection["id"]
)
assert collection_details["column_definitions"] == sample_sheet["column_definitions"]
# Verify that the output collection preserved the columns metadata
output_elements = collection_details["elements"]
+92
View File
@@ -5622,6 +5622,98 @@ test_data:
messages = subworkflow_invocation.get("messages", [])
assert len(messages) == 0, f"Expected no error messages, got: {messages}"
def test_run_subworkflow_with_boolean_parameter_in_when_condition(self):
"""Test boolean false parameter passed to subworkflow with when condition.
This test verifies that boolean parameters (especially false) are properly
passed from parent to subworkflow when the subworkflow has:
1. Delayed scheduling (via $link)
2. A when condition that uses the boolean parameter
Previously, false values were converted to None in the when expression evaluation,
causing "when_not_boolean" errors.
"""
with self.dataset_populator.test_history() as history_id:
workflow = """
class: GalaxyWorkflow
inputs:
should_run:
type: boolean
some_file:
type: data
steps:
nested_workflow:
in:
subworkflow_should_run: should_run
subworkflow_file: some_file
run:
class: GalaxyWorkflow
inputs:
subworkflow_should_run: boolean
subworkflow_file: data
steps:
expression:
tool_id: expression_forty_two
state: {}
conditional_step:
tool_id: cheetah_casting
in:
subworkflow_should_run: subworkflow_should_run
state:
floattest: 3.14
inttest:
$link: expression/out1
when: $(inputs.subworkflow_should_run)
test_data:
some_file:
value: 1.bed
type: File
should_run:
value: false
type: raw
"""
summary = self._run_workflow(workflow, history_id=history_id, wait=True, assert_ok=True)
# Verify parent workflow executed successfully
parent_invocation = self.workflow_populator.get_invocation(summary.invocation_id, step_details=True)
assert parent_invocation["state"] == "scheduled"
# Find the subworkflow step and get its invocation
subworkflow_step = None
for step in parent_invocation["steps"]:
if step.get("subworkflow_invocation_id"):
subworkflow_step = step
break
assert subworkflow_step is not None, "No subworkflow step found"
subworkflow_invocation_id = subworkflow_step["subworkflow_invocation_id"]
subworkflow_invocation = self.workflow_populator.get_invocation(
subworkflow_invocation_id, step_details=True
)
# The subworkflow should have succeeded
assert (
subworkflow_invocation["state"] == "scheduled"
), f"Expected subworkflow to succeed, got state: {subworkflow_invocation['state']}"
# Should not have error messages (previously failed with "when_not_boolean")
messages = subworkflow_invocation.get("messages", [])
assert len(messages) == 0, f"Expected no error messages, got: {messages}"
# Find the conditional step in the subworkflow and verify it was skipped
# (when condition was false, so step should not execute)
conditional_step = None
for step in subworkflow_invocation["steps"]:
if step.get("workflow_step_label") == "conditional_step":
conditional_step = step
break
assert conditional_step is not None, "Conditional step not found in subworkflow"
# The step should have been skipped because should_run=false
assert len(conditional_step["jobs"]) == 0 or all(
j["state"] == "skipped" for j in conditional_step["jobs"]
), "Expected conditional step to be skipped when should_run=false"
def test_run_with_non_optional_data_unspecified_fails_invocation(self):
with self.dataset_populator.test_history() as history_id:
error = self._run_jobs(
@@ -247,6 +247,7 @@ class MockCollection:
self.collection_type = collection_type
self.elements = elements
self.populated = True
self.column_definitions = None
class MockCollectionElement:
@@ -94,8 +94,7 @@ class TestWorkflowProgress(TestCase):
workflow_invocation_step.state = "scheduled"
workflow_invocation_step.workflow_step = self._step(i)
assert step_id == self._step(i).id
# workflow_invocation_step.workflow_invocation = self.invocation
self.invocation.steps.append(workflow_invocation_step)
workflow_invocation_step.workflow_invocation = self.invocation
workflow_invocation_step_state = model.WorkflowRequestStepState()
workflow_invocation_step_state.workflow_step_id = step_id
@@ -111,6 +110,7 @@ class TestWorkflowProgress(TestCase):
else:
workflow_invocation_step = model.WorkflowInvocationStep()
workflow_invocation_step.workflow_step = self._step(index)
workflow_invocation_step.workflow_invocation = self.invocation
return workflow_invocation_step
def test_connect_data_input(self):
@@ -211,6 +211,7 @@ class TestWorkflowProgress(TestCase):
subworkflow_invocation_step.workflow_step_id = subworkflow_input_step.id
subworkflow_invocation_step.state = "new"
subworkflow_invocation_step.workflow_step = subworkflow_input_step
subworkflow_invocation_step.workflow_invocation = subworkflow_invocation
subworkflow_progress.set_outputs_for_input(subworkflow_invocation_step)