Skip to content

Commit 102f8fa

Browse files
committed
feat: add author and email metadata support in WDL tasks and workflows
1 parent 79819b5 commit 102f8fa

4 files changed

Lines changed: 87 additions & 3 deletions

File tree

src/domain/value_objects.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,21 @@ class WDLTask:
9191
outputs: List[WDLOutput] = field(default_factory=list)
9292
command: Optional[WDLCommand] = None
9393
runtime: Dict[str, str] = field(default_factory=dict)
94+
meta: Dict[str, str] = field(default_factory=dict)
95+
author: Optional[str] = None
96+
email: Optional[str] = None
9497

9598
@property
9699
def has_description(self) -> bool:
97100
return self.description is not None
101+
102+
@property
103+
def has_author(self) -> bool:
104+
return self.author is not None
105+
106+
@property
107+
def has_email(self) -> bool:
108+
return self.email is not None
98109

99110
@property
100111
def has_inputs(self) -> bool:
@@ -156,10 +167,21 @@ class WDLWorkflow:
156167
calls: List[WDLCall] = field(default_factory=list)
157168
docker_images: List[WDLDockerImage] = field(default_factory=list)
158169
mermaid_graph: Optional[str] = None # Mermaid diagram representation
170+
meta: Dict[str, str] = field(default_factory=dict)
171+
author: Optional[str] = None
172+
email: Optional[str] = None
159173

160174
@property
161175
def has_description(self) -> bool:
162176
return self.description is not None
177+
178+
@property
179+
def has_author(self) -> bool:
180+
return self.author is not None
181+
182+
@property
183+
def has_email(self) -> bool:
184+
return self.email is not None
163185

164186
@property
165187
def has_inputs(self) -> bool:

src/infrastructure/parsing/ast_mapper.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ def map_workflow(self, workflow: WDL.Tree.Workflow, imports: List[WDLImport], wd
5353
name = workflow.name
5454
description = self._extract_description(workflow)
5555
parameter_meta = self._extract_parameter_meta(workflow)
56+
meta = self._parse_meta(workflow)
57+
58+
# Extract author and email from meta
59+
author = meta.get('author')
60+
email = meta.get('email')
5661

5762
# Parse inputs and outputs
5863
inputs = [self._parse_input(inp.value, parameter_meta) for inp in workflow.available_inputs]
@@ -75,16 +80,24 @@ def map_workflow(self, workflow: WDL.Tree.Workflow, imports: List[WDLImport], wd
7580
calls=calls,
7681
docker_images=docker_images,
7782
mermaid_graph=mermaid_graph,
83+
meta=meta,
84+
author=author,
85+
email=email,
7886
)
7987

8088
def map_task(self, task: WDL.Tree.Task) -> WDLTask:
8189
"""Map a miniwdl Task object to domain WDLTask."""
8290
name = task.name
8391
description = self._extract_description(task)
84-
meta = self._extract_parameter_meta(task)
92+
parameter_meta = self._extract_parameter_meta(task)
93+
meta = self._parse_meta(task)
94+
95+
# Extract author and email from meta
96+
author = meta.get('author')
97+
email = meta.get('email')
8598

8699
# Parse inputs and outputs
87-
inputs = [self._parse_input(inp.value, meta) for inp in task.available_inputs]
100+
inputs = [self._parse_input(inp.value, parameter_meta) for inp in task.available_inputs]
88101
outputs = [self._parse_output(out) for out in task.outputs] if task.outputs else []
89102

90103
# Parse command
@@ -100,6 +113,9 @@ def map_task(self, task: WDL.Tree.Task) -> WDLTask:
100113
outputs=outputs,
101114
command=command,
102115
runtime=runtime,
116+
meta=meta,
117+
author=author,
118+
email=email,
103119
)
104120

105121
@staticmethod
@@ -287,6 +303,17 @@ def _extract_description(obj) -> Optional[str]:
287303
if "description" in obj.meta:
288304
return str(obj.meta["description"])
289305
return None
306+
307+
@staticmethod
308+
def _parse_meta(obj) -> Dict[str, str]:
309+
"""Parse meta section and return all metadata as dict."""
310+
meta = {}
311+
312+
if hasattr(obj, "meta") and obj.meta:
313+
for key, value in obj.meta.items():
314+
meta[key] = str(value)
315+
316+
return meta
290317

291318
@staticmethod
292319
def _extract_parameter_meta(obj) -> dict[str, str]:

src/infrastructure/rendering/templates/components/document.html

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,39 @@ <h2 style="margin: 0;">Workflow: {{ workflow_name }}</h2>
1515
{% endcall %}
1616
</div>
1717
{%- endmacro %}
18+
19+
{# Author and email info banner #}
20+
{% macro author_info(author=none, email=none) %}
21+
{% if author or email %}
22+
<div style="margin: 12px 0;
23+
padding: 10px 14px;
24+
background: rgba(88, 166, 255, 0.08);
25+
border-radius: 6px;
26+
border-left: 3px solid #58a6ff;
27+
font-size: 14px;">
28+
<div style="display: flex; align-items: center; gap: 12px; flex-wrap: wrap;">
29+
{% if author %}
30+
<div style="display: flex; align-items: center; gap: 6px;">
31+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
32+
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
33+
<circle cx="12" cy="7" r="4"></circle>
34+
</svg>
35+
<span style="color: #c9d1d9;"><strong style="color: #58a6ff;">Author:</strong> {{ author }}</span>
36+
</div>
37+
{% endif %}
38+
{% if email %}
39+
<div style="display: flex; align-items: center; gap: 6px;">
40+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
41+
<rect x="2" y="4" width="20" height="16" rx="2"></rect>
42+
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"></path>
43+
</svg>
44+
<a href="mailto:{{ email }}" style="color: #58a6ff; text-decoration: none;">{{ email }}</a>
45+
</div>
46+
{% endif %}
47+
</div>
48+
</div>
49+
{% endif %}
50+
{%- endmacro %}
1851
{# Tasks section header with action button #}
1952
{% macro tasks_section_header(has_source=false) %}
2053
<div style="display: flex;
@@ -174,6 +207,7 @@ <h4>
174207
<div class="card" id="task-{{ task.name }}" style="margin-bottom: 20px;">
175208
<h3>{{ task_badge() }} {{ task.name }}</h3>
176209
{% if task.description %}<p class="description">{{ task.description }}</p>{% endif %}
210+
{{ author_info(task.author, task.email) }}
177211
{% if task.has_inputs %}
178212
<h4>Inputs</h4>
179213
{{ inputs_table(task.inputs) }}

src/infrastructure/rendering/templates/document.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{% extends "base.html" %}
22
{% from "macros/badges.html" import workflow_badge, task_badge, file_badge %}
33
{% from "macros/tables.html" import inputs_table, outputs_table, imports_table, document_info_table %}
4-
{% from "components/document.html" import workflow_section_header, tasks_section_header, subworkflow_usage_banner, call_block, docker_images_grid, task_card %}
4+
{% from "components/document.html" import workflow_section_header, tasks_section_header, author_info, subworkflow_usage_banner, call_block, docker_images_grid, task_card %}
55
{% from "components/modals.html" import graph_modal, source_modal %}
66
{% block title %}{{ doc.name }} - WDL Atlas{% endblock %}
77
{% block header %}
@@ -68,6 +68,7 @@ <h2>Imports</h2>
6868
<div class="card section" id="workflow">
6969
{{ workflow_section_header(doc.workflow.name, doc.workflow.has_graph, doc.source_code) }}
7070
{% if doc.workflow.description %}<p class="description">{{ doc.workflow.description }}</p>{% endif %}
71+
{{ author_info(doc.workflow.author, doc.workflow.email) }}
7172
{% if workflow_call_info %}{{ subworkflow_usage_banner(workflow_call_info, doc.relative_path) }}{% endif %}
7273
{% if doc.workflow.has_inputs %}
7374
<h3 id="inputs">Inputs</h3>

0 commit comments

Comments
 (0)