Commit ·
ff97939
1
Parent(s): 0510038
Fix Mermaid visualization rendering and SSR mode
Browse files- Fixed Mermaid.js rendering to properly display graphs (not just text)
- Added unique IDs for each diagram to prevent conflicts
- Disabled SSR mode to fix Spaces hot-reloading error
- Updated tests to match new HTML structure
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- app.py +47 -23
- tests/test_app.py +4 -4
app.py
CHANGED
|
@@ -45,23 +45,54 @@ EXPORT_FORMATS = ["OpenLineage", "Collibra", "Purview", "Alation"]
|
|
| 45 |
# ============================================================================
|
| 46 |
|
| 47 |
def render_mermaid(viz_code: str) -> str:
|
| 48 |
-
"""
|
|
|
|
| 49 |
safe_viz = viz_code.replace("<", "<").replace(">", ">")
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
<div class="mermaid">{safe_viz}</div>
|
| 62 |
</div>
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
|
| 67 |
# ============================================================================
|
|
@@ -505,13 +536,6 @@ with gr.Blocks(
|
|
| 505 |
mcp_status = gr.Textbox(label="Connection Status", interactive=False)
|
| 506 |
test_btn.click(fn=test_mcp_connection, inputs=[mcp_server, mcp_api_key], outputs=[mcp_status])
|
| 507 |
|
| 508 |
-
# Mermaid.js loader
|
| 509 |
-
gr.HTML(
|
| 510 |
-
value='<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>'
|
| 511 |
-
'<script>mermaid.initialize({startOnLoad:false, theme:"default"});</script>',
|
| 512 |
-
visible=False
|
| 513 |
-
)
|
| 514 |
-
|
| 515 |
# Main Tabs
|
| 516 |
with gr.Tabs():
|
| 517 |
# Tab 1: Text/File Input
|
|
@@ -718,4 +742,4 @@ with gr.Blocks(
|
|
| 718 |
|
| 719 |
# Launch
|
| 720 |
if __name__ == "__main__":
|
| 721 |
-
demo.launch()
|
|
|
|
| 45 |
# ============================================================================
|
| 46 |
|
| 47 |
def render_mermaid(viz_code: str) -> str:
|
| 48 |
+
"""Render mermaid diagram using embedded script with proper initialization."""
|
| 49 |
+
# Escape HTML-sensitive characters but preserve newlines for mermaid
|
| 50 |
safe_viz = viz_code.replace("<", "<").replace(">", ">")
|
| 51 |
+
|
| 52 |
+
# Generate unique ID for this diagram
|
| 53 |
+
import random
|
| 54 |
+
diagram_id = f"mermaid-{random.randint(10000, 99999)}"
|
| 55 |
+
|
| 56 |
+
# Complete HTML with embedded Mermaid.js and proper rendering
|
| 57 |
+
html = f'''
|
| 58 |
+
<div id="{diagram_id}-container" style="background: white; padding: 20px; border-radius: 8px; min-height: 200px;">
|
| 59 |
+
<div id="{diagram_id}" class="mermaid">
|
| 60 |
+
{safe_viz}
|
| 61 |
+
</div>
|
|
|
|
| 62 |
</div>
|
| 63 |
+
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
|
| 64 |
+
<script>
|
| 65 |
+
(function() {{
|
| 66 |
+
// Initialize mermaid with configuration
|
| 67 |
+
if (typeof mermaid !== 'undefined') {{
|
| 68 |
+
mermaid.initialize({{
|
| 69 |
+
startOnLoad: false,
|
| 70 |
+
theme: 'default',
|
| 71 |
+
securityLevel: 'loose',
|
| 72 |
+
flowchart: {{
|
| 73 |
+
useMaxWidth: true,
|
| 74 |
+
htmlLabels: true,
|
| 75 |
+
curve: 'basis'
|
| 76 |
+
}}
|
| 77 |
+
}});
|
| 78 |
+
|
| 79 |
+
// Render the specific diagram
|
| 80 |
+
try {{
|
| 81 |
+
const element = document.getElementById('{diagram_id}');
|
| 82 |
+
if (element) {{
|
| 83 |
+
mermaid.init(undefined, element);
|
| 84 |
+
}}
|
| 85 |
+
}} catch (e) {{
|
| 86 |
+
console.error('Mermaid rendering error:', e);
|
| 87 |
+
}}
|
| 88 |
+
}} else {{
|
| 89 |
+
// Retry if mermaid not loaded yet
|
| 90 |
+
setTimeout(arguments.callee, 100);
|
| 91 |
+
}}
|
| 92 |
+
}})();
|
| 93 |
+
</script>
|
| 94 |
+
'''
|
| 95 |
+
return html
|
| 96 |
|
| 97 |
|
| 98 |
# ============================================================================
|
|
|
|
| 536 |
mcp_status = gr.Textbox(label="Connection Status", interactive=False)
|
| 537 |
test_btn.click(fn=test_mcp_connection, inputs=[mcp_server, mcp_api_key], outputs=[mcp_status])
|
| 538 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 539 |
# Main Tabs
|
| 540 |
with gr.Tabs():
|
| 541 |
# Tab 1: Text/File Input
|
|
|
|
| 742 |
|
| 743 |
# Launch
|
| 744 |
if __name__ == "__main__":
|
| 745 |
+
demo.launch(ssr_mode=False)
|
tests/test_app.py
CHANGED
|
@@ -12,7 +12,7 @@ class TestLineageExtractors(unittest.TestCase):
|
|
| 12 |
def test_render_mermaid_wraps_and_inits(self):
|
| 13 |
viz = "graph TD\n A --> B"
|
| 14 |
html = render_mermaid(viz)
|
| 15 |
-
self.assertIn('
|
| 16 |
self.assertIn('graph TD', html)
|
| 17 |
self.assertIn('mermaid.init', html)
|
| 18 |
|
|
@@ -22,7 +22,7 @@ class TestLineageExtractors(unittest.TestCase):
|
|
| 22 |
html, summary = extract_lineage_from_text(sample_json, "Custom JSON", "Mermaid")
|
| 23 |
self.assertIsInstance(html, str)
|
| 24 |
self.assertIsInstance(summary, str)
|
| 25 |
-
self.assertIn('
|
| 26 |
self.assertIn('Parsed', summary)
|
| 27 |
|
| 28 |
def test_extract_lineage_from_text_empty_input(self):
|
|
@@ -34,12 +34,12 @@ class TestLineageExtractors(unittest.TestCase):
|
|
| 34 |
|
| 35 |
def test_extract_lineage_from_bigquery_returns_html_and_summary(self):
|
| 36 |
html, summary = extract_lineage_from_bigquery("proj", "SELECT 1", "key", "Mermaid")
|
| 37 |
-
self.assertIn('
|
| 38 |
self.assertIn('BigQuery', summary)
|
| 39 |
|
| 40 |
def test_extract_lineage_from_url_returns_html_and_summary(self):
|
| 41 |
html, summary = extract_lineage_from_url("https://example.com", "Mermaid")
|
| 42 |
-
self.assertIn('
|
| 43 |
# Summary can be either 'Lineage' or 'Parsed' depending on response
|
| 44 |
self.assertTrue('Lineage' in summary or 'Parsed' in summary)
|
| 45 |
|
|
|
|
| 12 |
def test_render_mermaid_wraps_and_inits(self):
|
| 13 |
viz = "graph TD\n A --> B"
|
| 14 |
html = render_mermaid(viz)
|
| 15 |
+
self.assertIn('class="mermaid"', html)
|
| 16 |
self.assertIn('graph TD', html)
|
| 17 |
self.assertIn('mermaid.init', html)
|
| 18 |
|
|
|
|
| 22 |
html, summary = extract_lineage_from_text(sample_json, "Custom JSON", "Mermaid")
|
| 23 |
self.assertIsInstance(html, str)
|
| 24 |
self.assertIsInstance(summary, str)
|
| 25 |
+
self.assertIn('class="mermaid"', html)
|
| 26 |
self.assertIn('Parsed', summary)
|
| 27 |
|
| 28 |
def test_extract_lineage_from_text_empty_input(self):
|
|
|
|
| 34 |
|
| 35 |
def test_extract_lineage_from_bigquery_returns_html_and_summary(self):
|
| 36 |
html, summary = extract_lineage_from_bigquery("proj", "SELECT 1", "key", "Mermaid")
|
| 37 |
+
self.assertIn('class="mermaid"', html)
|
| 38 |
self.assertIn('BigQuery', summary)
|
| 39 |
|
| 40 |
def test_extract_lineage_from_url_returns_html_and_summary(self):
|
| 41 |
html, summary = extract_lineage_from_url("https://example.com", "Mermaid")
|
| 42 |
+
self.assertIn('class="mermaid"', html)
|
| 43 |
# Summary can be either 'Lineage' or 'Parsed' depending on response
|
| 44 |
self.assertTrue('Lineage' in summary or 'Parsed' in summary)
|
| 45 |
|