@@ -242,40 +242,6 @@ def get_graph_svg(cls):
242242
243243 get_graph_svg .short_description = t ("graph" )
244244
245- @classmethod
246- def get_graph_mermaid (cls , color = "black" ):
247- """
248- Return workflow graph as Mermaid diagram syntax.
249-
250- This can be used with MermaidJS for client-side rendering in browsers.
251-
252- Returns:
253- (str): Mermaid diagram syntax.
254- """
255- lines = [f"graph { cls .rankdir } " ]
256-
257- # Add nodes
258- for name , node in cls .get_nodes ():
259- node_id = name .replace (" " , "_" )
260- # Keep original name with spaces for label
261- label = name .replace ("_" , " " )
262-
263- # Determine shape based on node type
264- if node .type == HUMAN :
265- # Rounded rectangle for human tasks
266- lines .append (f" { node_id } ({ label } )" )
267- else :
268- # Rectangle for machine tasks
269- lines .append (f" { node_id } [{ label } ]" )
270-
271- # Add edges
272- for start , end in cls .edges :
273- start_id = start .name .replace (" " , "_" )
274- end_id = end .name .replace (" " , "_" )
275- lines .append (f" { start_id } --> { end_id } " )
276-
277- return "\n " .join (lines )
278-
279245 def get_instance_graph (self ):
280246 """Return workflow instance graph."""
281247 graph = self .get_graph (color = "#888888" )
@@ -377,8 +343,8 @@ def get_instance_graph_mermaid(self):
377343 """
378344 lines = [f"graph { self .rankdir } " ]
379345 node_styles = []
380- edge_styles = []
381- edge_index = 0
346+ edge_styles = {} # Map of (start, end) -> style
347+ edge_list = [] # List to maintain order of edges
382348
383349 names = dict (self .get_nodes ()).keys ()
384350
@@ -388,40 +354,43 @@ def get_instance_graph_mermaid(self):
388354 # Keep original name with spaces for label
389355 label = name .replace ("_" , " " )
390356
391- # Determine shape based on node type
357+ # Determine shape based on node type, quote IDs to handle reserved words
392358 if node .type == HUMAN :
393- lines .append (f" { node_id } ({ label } )" )
359+ lines .append (f" ' { node_id } ' ({ label } )" )
394360 else :
395- lines .append (f" { node_id } [{ label } ]" )
361+ lines .append (f" ' { node_id } ' [{ label } ]" )
396362
397363 # Default gray styling for nodes not yet processed
398- node_styles .append (f" style { node_id } fill:#f9f9f9,stroke:#999,color:#999" )
364+ node_styles .append (f" style ' { node_id } ' fill:#f9f9f9,stroke:#999,color:#999" )
399365
400- # Add edges from workflow definition ( gray style)
366+ # Add edges from workflow definition with default gray style
401367 for start , end in self .edges :
402368 start_id = start .name .replace (" " , "_" )
403369 end_id = end .name .replace (" " , "_" )
404- lines .append (f" { start_id } --> { end_id } " )
405- edge_styles .append (f" linkStyle { edge_index } stroke:#999" )
406- edge_index += 1
370+ edge_key = (start_id , end_id )
371+ if edge_key not in edge_styles :
372+ edge_list .append (edge_key )
373+ edge_styles [edge_key ] = "stroke:#999"
407374
408375 # Process actual tasks to highlight active/completed states
409376 for task in self .task_set .filter (name__in = names ):
410377 node_id = task .name .replace (" " , "_" )
411378
412379 # Active tasks (not completed) get bold black styling
413380 if not task .completed :
414- node_styles .append (f" style { node_id } fill:#fff,stroke:#000,stroke-width:3px,color:#000" )
381+ node_styles .append (f" style ' { node_id } ' fill:#fff,stroke:#000,stroke-width:3px,color:#000" )
415382 else :
416383 # Completed tasks get normal black styling
417- node_styles .append (f" style { node_id } fill:#fff,stroke:#000,stroke-width:2px,color:#000" )
384+ node_styles .append (f" style ' { node_id } ' fill:#fff,stroke:#000,stroke-width:2px,color:#000" )
418385
419- # Add edges for actual task connections (black style)
386+ # Update edge styling for actual task connections (black style)
420387 for child in task .child_task_set .exclude (name = "override" ):
421388 child_id = child .name .replace (" " , "_" )
422- lines .append (f" { node_id } --> { child_id } " )
423- edge_styles .append (f" linkStyle { edge_index } stroke:#000,stroke-width:2px" )
424- edge_index += 1
389+ edge_key = (node_id , child_id )
390+ if edge_key not in edge_styles :
391+ edge_list .append (edge_key )
392+ # Update styling to black (overrides gray)
393+ edge_styles [edge_key ] = "stroke:#000,stroke-width:2px"
425394
426395 # Handle override tasks
427396 for task in self .task_set .filter (name = "override" ).prefetch_related (
@@ -430,57 +399,75 @@ def get_instance_graph_mermaid(self):
430399 override_id = f"override_{ task .pk } "
431400 override_label = f"override { task .pk } "
432401
433- # Add override node with dashed style
434- lines .append (f" { override_id } ({ override_label } )" )
435- node_styles .append (f" style { override_id } fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray:5 5,color:#000" )
402+ # Add override node with dashed style, quote ID
403+ lines .append (f" ' { override_id } ' ({ override_label } )" )
404+ node_styles .append (f" style ' { override_id } ' fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray:5 5,color:#000" )
436405
437406 # Add dashed edges for override connections
438407 for parent in task .parent_task_set .all ():
439408 parent_id = parent .name .replace (" " , "_" )
440- lines .append (f" { parent_id } -.-> { override_id } " )
441- edge_styles .append (f" linkStyle { edge_index } stroke:#000,stroke-dasharray:5 5" )
442- edge_index += 1
409+ edge_key = (parent_id , override_id )
410+ if edge_key not in edge_styles :
411+ edge_list .append (edge_key )
412+ edge_styles [edge_key ] = "stroke:#000,stroke-dasharray:5 5"
443413
444414 for child in task .child_task_set .all ():
445415 child_id = child .name .replace (" " , "_" )
446- lines .append (f" { override_id } -.-> { child_id } " )
447- edge_styles .append (f" linkStyle { edge_index } stroke:#000,stroke-dasharray:5 5" )
448- edge_index += 1
416+ edge_key = (override_id , child_id )
417+ if edge_key not in edge_styles :
418+ edge_list .append (edge_key )
419+ edge_styles [edge_key ] = "stroke:#000,stroke-dasharray:5 5"
449420
450421 # Handle obsolete/custom tasks (not in workflow definition)
451422 for task in self .task_set .exclude (name__in = names ).exclude (name = "override" ):
452423 node_id = task .name .replace (" " , "_" )
453424 # Keep original name with spaces for label
454425 label = task .name .replace ("_" , " " )
455426
456- # Determine shape based on node type
427+ # Determine shape based on node type, quote IDs
457428 if task .type == HUMAN :
458- lines .append (f" { node_id } ({ label } )" )
429+ lines .append (f" ' { node_id } ' ({ label } )" )
459430 else :
460- lines .append (f" { node_id } [{ label } ]" )
431+ lines .append (f" ' { node_id } ' [{ label } ]" )
461432
462433 # Dashed styling for obsolete tasks
463434 if not task .completed :
464- node_styles .append (f" style { node_id } fill:#fff,stroke:#000,stroke-width:3px,stroke-dasharray:5 5,color:#000" )
435+ node_styles .append (f" style ' { node_id } ' fill:#fff,stroke:#000,stroke-width:3px,stroke-dasharray:5 5,color:#000" )
465436 else :
466- node_styles .append (f" style { node_id } fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray:5 5,color:#000" )
437+ node_styles .append (f" style ' { node_id } ' fill:#fff,stroke:#000,stroke-width:2px,stroke-dasharray:5 5,color:#000" )
467438
468439 # Add dashed edges for obsolete task connections
469440 for parent in task .parent_task_set .all ():
470441 parent_id = parent .name .replace (" " , "_" )
471- lines .append (f" { parent_id } -.-> { node_id } " )
472- edge_styles .append (f" linkStyle { edge_index } stroke:#000,stroke-dasharray:5 5" )
473- edge_index += 1
442+ edge_key = (parent_id , node_id )
443+ if edge_key not in edge_styles :
444+ edge_list .append (edge_key )
445+ edge_styles [edge_key ] = "stroke:#000,stroke-dasharray:5 5"
474446
475447 for child in task .child_task_set .all ():
476448 child_id = child .name .replace (" " , "_" )
477- lines .append (f" { node_id } -.-> { child_id } " )
478- edge_styles .append (f" linkStyle { edge_index } stroke:#000,stroke-dasharray:5 5" )
479- edge_index += 1
449+ edge_key = (node_id , child_id )
450+ if edge_key not in edge_styles :
451+ edge_list .append (edge_key )
452+ edge_styles [edge_key ] = "stroke:#000,stroke-dasharray:5 5"
453+
454+ # Add edges to output (using dotted arrow for dashed edges)
455+ for start_id , end_id in edge_list :
456+ style = edge_styles [(start_id , end_id )]
457+ if "dasharray" in style :
458+ # Use dotted arrow for dashed edges
459+ lines .append (f" '{ start_id } ' -.-> '{ end_id } '" )
460+ else :
461+ # Use solid arrow for normal edges
462+ lines .append (f" '{ start_id } ' --> '{ end_id } '" )
480463
481464 # Add all styling at the end
482465 lines .extend (node_styles )
483- lines .extend (edge_styles )
466+
467+ # Add edge styling
468+ for idx , (start_id , end_id ) in enumerate (edge_list ):
469+ style = edge_styles [(start_id , end_id )]
470+ lines .append (f" linkStyle { idx } { style } " )
484471
485472 return "\n " .join (lines )
486473
0 commit comments