Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 14 additions & 1 deletion joeflow/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,25 @@ def get_inlines(self, *args, **kwargs):

def get_readonly_fields(self, *args, **kwargs):
return [
"get_instance_graph_svg",
"display_workflow_diagram",
*super().get_readonly_fields(*args, **kwargs),
"modified",
"created",
]

@admin.display(description="Workflow Diagram")
def display_workflow_diagram(self, obj):
"""Display workflow diagram using MermaidJS for client-side rendering."""
if obj.pk:
# Get Mermaid diagram syntax
mermaid_syntax = obj.get_instance_graph_mermaid()
# Wrap in div with mermaid class for client-side rendering
return format_html(
'<div class="mermaid-diagram"><div class="mermaid">{}</div></div>',
mermaid_syntax
)
return ""

@transaction.atomic()
def save_model(self, request, obj, form, change):
super().save_model(request, obj, form, change)
Expand Down
68 changes: 68 additions & 0 deletions joeflow/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,39 @@ def get_graph_svg(cls):

get_graph_svg.short_description = t("graph")

@classmethod
def get_graph_mermaid(cls, color="black"):
"""
Return workflow graph as Mermaid diagram syntax.

This can be used with MermaidJS for client-side rendering in browsers.

Returns:
(str): Mermaid diagram syntax.
"""
lines = [f"graph {cls.rankdir}"]

# Add nodes
for name, node in cls.get_nodes():
node_id = name.replace(" ", "_")
label = name

# Determine shape based on node type
if node.type == HUMAN:
# Rounded rectangle for human tasks
lines.append(f" {node_id}({label})")
else:
# Rectangle for machine tasks
lines.append(f" {node_id}[{label}]")

# Add edges
for start, end in cls.edges:
start_id = start.name.replace(" ", "_")
end_id = end.name.replace(" ", "_")
lines.append(f" {start_id} --> {end_id}")

return "\n".join(lines)

def get_instance_graph(self):
"""Return workflow instance graph."""
graph = self.get_graph(color="#888888")
Expand Down Expand Up @@ -332,6 +365,41 @@ def get_instance_graph_svg(self, output_format="svg"):

get_instance_graph_svg.short_description = t("instance graph")

def get_instance_graph_mermaid(self):
"""
Return instance graph as Mermaid diagram syntax.

This can be used with MermaidJS for client-side rendering in admin.

Returns:
(str): Mermaid diagram syntax for the instance graph.
"""
lines = [f"graph {self.rankdir}"]

names = dict(self.get_nodes()).keys()

# Add all nodes from workflow definition
for name, node in self.get_nodes():
node_id = name.replace(" ", "_")
label = name

# Determine shape based on node type
if node.type == HUMAN:
lines.append(f" {node_id}({label})")
else:
lines.append(f" {node_id}[{label}]")

# Add edges from workflow definition
for start, end in self.edges:
start_id = start.name.replace(" ", "_")
end_id = end.name.replace(" ", "_")
lines.append(f" {start_id} --> {end_id}")

# TODO: Add styling for completed/active tasks
# This would require additional Mermaid syntax for node styling

return "\n".join(lines)

def cancel(self, user=None):
self.task_set.cancel(user)

Expand Down
10 changes: 10 additions & 0 deletions joeflow/templates/admin/change_form.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{% extends "admin/change_form.html" %}

{% block extrahead %}
{{ block.super }}
<!-- Mermaid JS for rendering workflow diagrams -->
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
mermaid.initialize({ startOnLoad: true });
</script>
{% endblock %}
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ requires-python = ">=3.9"
dependencies = [
"django>=2.2",
"django-appconf",
"graphviz>=0.18",
]

[project.optional-dependencies]
Expand All @@ -74,6 +73,10 @@ docs = [
"dramatiq",
"django_dramatiq",
"redis",
"graphviz>=0.18",
]
graphviz = [
"graphviz>=0.18",
]
reversion = [
"django-reversion",
Expand Down