diff --git a/.github/workflows/gh-page-preview.yml b/.github/workflows/gh-page-preview.yml
index 11566e1b6..a6deba400 100644
--- a/.github/workflows/gh-page-preview.yml
+++ b/.github/workflows/gh-page-preview.yml
@@ -13,8 +13,6 @@ jobs:
steps:
- name: Checkout PR Branch
uses: actions/checkout@v4
- with:
- ref: ${{ github.head_ref }}
- name: Set up Python
uses: actions/setup-python@v5
@@ -72,4 +70,4 @@ jobs:
run: |
git rm -rf __preview/pr-${{ github.event.pull_request.number }}
git commit -m "Remove preview for PR #${{ github.event.pull_request.number }}"
- git push origin gh-pages
\ No newline at end of file
+ git push origin gh-pages
diff --git a/.gitignore b/.gitignore
index 9ed3cfffe..1bd11b884 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ tool_test_output.html
tool_test_output.json
.DS_Store
workflow_manifest.json
+.vscode
diff --git a/scripts/create_mermaid.py b/scripts/create_mermaid.py
new file mode 100644
index 000000000..9a6f84fdb
--- /dev/null
+++ b/scripts/create_mermaid.py
@@ -0,0 +1,110 @@
+import argparse
+import json
+import os
+from typing import Literal
+
+from gxformat2.converter import python_to_workflow
+from gxformat2.yaml import ordered_load
+
+STEP_TYPE_TO_SHAPE = {
+ "data_input": "@{ shape: doc }",
+ "data_collection_input": "@{ shape: docs }",
+ "parameter_input": "@{ shape: lean-l }",
+ "tool": "@{ shape: process }",
+ "subworkflow": "@{ shape: subprocess }",
+}
+STEP_TYPE_TO_SYMBOL = {
+ "data_input": "ℹ️ ",
+ "data_collection_input": "ℹ️ ",
+ "parameter_input": "ℹ️ ",
+ "subworkflow": "🛠️ ",
+ "tool": "",
+}
+
+
+def step_to_mermaid_item(
+ step_type: Literal[
+ "parameter_input", "data_input", "data_collection_input", "tool", "subworkflow"
+ ],
+ step_label: str,
+):
+ prefix = STEP_TYPE_TO_SYMBOL[step_type]
+ step_label_anchor = f'["{prefix}{step_label}"]'
+ shape = STEP_TYPE_TO_SHAPE.get(step_type, "")
+ return f"{step_label_anchor}{shape}"
+
+
+def workflow_to_mermaid_diagrams(workflow, workflows = None):
+ """
+ Converts a Galaxy workflow JSON to a Mermaid flowchart diagram.
+
+ Args:
+ workflow_json: The JSON representation of the Galaxy workflow.
+
+ Returns:
+ A string representing the Mermaid flowchart diagram.
+ """
+ if workflows is None:
+ workflows = {}
+
+ mermaid_diagram = ["graph LR"]
+
+ # Create a mapping of step IDs to their labels
+ id_step_labels = {
+ int(step["id"]): step.get("label") or step["name"] or step["content_id"] or step["id"]
+ for step in workflow["steps"].values()
+ }
+
+ # Iterate through each step and its connections
+ for step_id, step in workflow["steps"].items():
+ step_label = id_step_labels.get(int(step_id))
+ mermaid_diagram.append(
+ f'{step_id}{step_to_mermaid_item(step["type"], step_label)}'
+ )
+ for input_connection in step.get("input_connections", {}).values():
+ if not isinstance(input_connection, list):
+ input_connection = [input_connection]
+ for ic in input_connection:
+ mermaid_diagram.append(f"{ic['id']} --> {step_id}")
+
+ if step["type"] == "subworkflow":
+ workflow_to_mermaid_diagrams(step["subworkflow"], workflows=workflows)
+
+ workflows[workflow["name"]] = "\n".join(mermaid_diagram)
+
+ return workflows
+
+
+def walk_directory(directory):
+ """
+ Walk directory and call workflow_to_mermaid on each discovered .ga file.
+ """
+ for root, _, paths in os.walk(directory):
+ for path in paths:
+ if path.endswith((".ga", ".gxwf.yml")):
+ file_path = os.path.join(root, path)
+ with open(file_path, "r") as f:
+ workflow_data = ordered_load(f)
+ if workflow_data.get("class") == "GalaxyWorkflow":
+ workflow_data = python_to_workflow(workflow_data, galaxy_interface=None, workflow_directory=os.path.dirname(file_path))
+ mermaid_diagrams = workflow_to_mermaid_diagrams(workflow_data)
+
+ markdown_items = ["# Workflow diagrams\n"]
+ for workflow_name, diagram in reversed(mermaid_diagrams.items()):
+ markdown_items.append(f"## {workflow_name}\n")
+ markdown_items.append(f"```mermaid\n{diagram}\n```\n")
+
+ mmd_path = f"{os.path.splitext(file_path)[0]}_diagrams.md"
+ with open(mmd_path, "w") as f:
+ f.write("\n".join(markdown_items))
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(description="Process files in a directory")
+ parser.add_argument("directory", type=str, help="Path to the input directory")
+ return parser.parse_args()
+
+
+if __name__ == "__main__":
+ args = parse_args()
+ walk_directory(args.directory)
diff --git a/scripts/workflow_manifest.py b/scripts/workflow_manifest.py
index b015f8f5f..73a370857 100644
--- a/scripts/workflow_manifest.py
+++ b/scripts/workflow_manifest.py
@@ -1,6 +1,19 @@
import os
import json
import yaml
+from create_mermaid import walk_directory
+
+
+def read_contents(path: str):
+ try:
+ with open(path) as f:
+ return f.read()
+ except FileNotFoundError:
+ print(f"No {os.path.basename(path)} at {path}")
+ except Exception as e:
+ print(
+ f"Error reading file {path}: {e}"
+ )
def find_and_load_compliant_workflows(directory):
@@ -57,28 +70,10 @@ def find_and_load_compliant_workflows(directory):
f"No workflow file: {os.path.join(root, workflow['primaryDescriptorPath'])}: {e}"
)
- # also try to load a README.md file for each workflow
- try:
- with open(os.path.join(root, "README.md")) as f:
- workflow["readme"] = f.read()
- # catch FileNotFound
- except FileNotFoundError:
- print(f"No README.md at {os.path.join(root, 'README.md')}")
- except Exception as e:
- print(
- f"Error reading file {os.path.join(root, 'README.md')}: {e}"
- )
-
- # also try to load a CHANGELOG.md file for each workflow
- try:
- with open(os.path.join(root, "CHANGELOG.md")) as f:
- workflow["changelog"] = f.read()
- except FileNotFoundError:
- print(f"No CHANGELOG.md at {os.path.join(root, 'CHANGELOG.md')}")
- except Exception as e:
- print(
- f"Error reading file {os.path.join(root, 'CHANGELOG.md')}: {e}"
- )
+ # load readme, changelog and diagrams
+ workflow["readme"] = read_contents(os.path.join(root, "README.md"))
+ workflow["changelog"] = read_contents(os.path.join(root, "CHANGELOG.md"))
+ workflow["diagrams"] = read_contents(f"{os.path.splitext(workflow_path)[0]}_diagrams.md")
dirname = os.path.dirname(workflow_path).split("/")[-1]
workflow["trsID"] = f"#workflow/github.com/iwc-workflows/{dirname}/{workflow['name'] or 'main'}"
@@ -108,5 +103,6 @@ def write_to_json(data, filename):
if __name__ == "__main__":
+ walk_directory("./workflows")
workflow_data = find_and_load_compliant_workflows("./workflows")
write_to_json(workflow_data, "workflow_manifest.json")
diff --git a/website/components/MarkdownRenderer.vue b/website/components/MarkdownRenderer.vue
new file mode 100644
index 000000000..eb7f7360d
--- /dev/null
+++ b/website/components/MarkdownRenderer.vue
@@ -0,0 +1,60 @@
+
+
+
+
+
diff --git a/website/models/workflow.ts b/website/models/workflow.ts
index c7460f7f7..d68e42b50 100644
--- a/website/models/workflow.ts
+++ b/website/models/workflow.ts
@@ -16,6 +16,7 @@ export interface Workflow {
definition: WorkflowDefinition;
readme: string;
changelog: string;
+ diagrams: string;
trsID: string;
}
diff --git a/website/package.json b/website/package.json
index 7ccf49e5f..4ceb98cc0 100644
--- a/website/package.json
+++ b/website/package.json
@@ -15,6 +15,7 @@
"@nuxt/ui": "^2.19.2",
"@pinia/nuxt": "^0.5.5",
"marked": "^14.1.1",
+ "mermaid": "^11.4.1",
"nuxt": "^3.11.2",
"nuxt-icon": "^1.0.0-beta.7",
"pinia": "^2.2.4",
diff --git a/website/pages/workflow/[id].vue b/website/pages/workflow/[id].vue
index 9f912e3a1..2badfc24f 100644
--- a/website/pages/workflow/[id].vue
+++ b/website/pages/workflow/[id].vue
@@ -1,7 +1,7 @@