galaxyproject / galaxyproject/gxformat2
Diagram builders drop step→step edges into steps that carry a distinct label
- Dominant language
- Python
- Stars
- 14
- Forks
- 7
- Avg merge
- 2h 49m
- Merged PRs (30d)
- 8
Description
> **Posted by Claude (AI assistant) on behalf of @jmchilton — not authored by them personally.**
## Summary
The Mermaid and Cytoscape diagram builders silently drop (or dangle) step→step edges whose source addresses an upstream step by its **`id`** when that step carries a distinct **`label:`**. The downstream node ends up orphaned (Mermaid) or the edge points at a non-existent node id (Cytoscape).
Both builders key step nodes by `step.label or step.id`, but resolve `in:` source references against `NormalizedFormat2.known_labels`, which is `{s.label or s.id} | {input ids}` — it does **not** include a step's `id` when a distinct `label` is present. So a source like `upstream/out` (referencing the step's `id`) matches nothing, falls through `resolve_source_reference`, and the resulting `step_label` never matches a keyed node.
Input→step edges are unaffected (inputs are both referenced and keyed by id), so diagrams just look deceptively sparse rather than erroring.
## Reproduction
```python
from gxformat2.mermaid import workflow_to_mermaid
from gxformat2.cytoscape import cytoscape_elements
wf = {
"class": "GalaxyWorkflow",
"inputs": {"in_file": {"type": "data"}},
"outputs": {},
"steps": {
"upstream": {
"label": "Upstream Tool With Label",
"tool_id": "toolshed.g2.bx.psu.edu/repos/iuc/up/up/1.0",
"tool_version": "1.0",
"in": {"input": "in_file"},
"out": [{"id": "out"}],
},
"downstream": {
"label": "Downstream Tool With Label",
"tool_id": "toolshed.g2.bx.psu.edu/repos/iuc/down/down/1.0",
"tool_version": "1.0",
"in": {"data": "upstream/out"}, # references upstream by its dict id
"out": [{"id": "result"}],
},
},
}
print(workflow_to_mermaid(wf))
```
### Mermaid — actual
```
graph LR
input_0>"in_file
data"]
step_0["Upstream Tool With Label"]
step_1["Downstream Tool With Label"]
input_0 --> step_0
```
`step_0 --> step_1` is missing — `downstream` renders orphaned.
### Cytoscape — actual
```
node ids: {'in_file', 'Downstream Tool With Label', 'Upstream Tool With Label'}
edge source='in_file' target='Upstream Tool With Label' dangling=False
edge source='upstream' target='Downstream Tool With Label' dangling=True
```
The second edge's `source='upstream'` is the raw dict id; no node carries that id (nodes are keyed by label), so it dangles.
### Expected
A `step_0 --> step_1` edge in Mermaid, and a Cytoscape edge whose `source` is the upstream node's render identity (`"Upstream Tool With Label"`) — both endpoints real nodes.
Verified against `gxformat2` @ `53f1624` (Python 3.12).
## Root cause (code pointers)
- `gxformat2/normalized/_format2.py` — `known_labels` = `{s.label or s.id} | {input ids}`; a step's `id` is omitted whenever it has a distinct `label`.
- `gxformat2/mermaid/_builder.py` — `step_ids[step.label or step.id] = node_id`; edges look up `step_ids.get(source_ref.step_label)`, which is `None` for an `id`-form source.
- `gxformat2/cytoscape/_builder.py` — same keying (`step_id = step.label or step.id`); `_step_edges` emits `source=ref.step_label` verbatim, producing the dangling edge.
## Suggested fix
Index steps by their `id` **as well as** their render identity, and fold an `id`-form source reference back to the render identity (`label or id`) the node is keyed by before lookup. The common case (`label == id`) is unchanged.
For reference, this was found and fixed in the TypeScript port (galaxy-tool-util-ts) by adding an `id → render-identity` map alongside `known_labels` and folding the resolved source through it in both builders. Happy to open a PR here mirroring that approach if useful.
## Note / open question
Whether such a workflow (explicit `label:` on a dict-form step, referenced elsewhere by the dict key) is *intended* to be valid format2 is worth confirming — but since these are visualization helpers, rendering the connection seems strictly better than silently orphaning a node or emitting a dangling edge.
Contributor guide
Research direction
Start with gxformat2/normalized/_format2.py, then trace the step keying and edge resolution in gxformat2/mermaid/_builder.py and gxformat2/cytoscape/_builder.py. Run the Python reproduction from the issue and verify that the upstream-to-downstream edge connects the real rendered nodes in both builders without dangling endpoints.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- data-visualization
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100