function generate_html_report
Maturity: 25
Generate HTML report from schema info
File:
/tf/active/vicechatdev/neo4j_schema_report.py
Lines:
277 - 1059
277 - 1059
Complexity:
moderate
moderate
Purpose
Generate HTML report from schema info
Source Code
def generate_html_report(schema_info, output_dir):
"""Generate HTML report from schema info"""
html_file = os.path.join(output_dir, "neo4j_schema_report.html")
css_file = os.path.join(output_dir, "neo4j_schema_report.css")
# Generate CSS as a separate file
with open(css_file, "w") as f:
f.write("""body { font-family: Arial, sans-serif; margin: 20px; }
h1, h2, h3 { color: #2c3e50; }
table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
tr:nth-child(even) { background-color: #f9f9f9; }
.section { margin-top: 30px; }
pre { background-color: #f5f5f5; padding: 10px; border-radius: 5px; overflow-x: auto; }
.card { border: 1px solid #ddd; border-radius: 5px; margin-bottom: 15px; padding: 15px; }
.card-header { font-weight: bold; margin-bottom: 10px; border-bottom: 1px solid #eee; padding-bottom: 5px; }
.count-badge { background-color: #3498db; color: white; border-radius: 10px; padding: 2px 8px; font-size: 0.8em; }
.toc { background-color: #f8f9fa; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
.toc ul { list-style-type: none; padding-left: 20px; }
.toc li { margin: 5px 0; }
.toc a { text-decoration: none; color: #3498db; }
.toc a:hover { text-decoration: underline; }
#schema-visualization {
width: 100%;
height: 600px;
border: 1px solid #ddd;
background-color: #f9f9f9;
margin-top: 20px;
}
.controls {
margin: 10px 0;
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.controls button {
padding: 5px 10px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.controls button:hover {
background-color: #45a049;
}
.legend {
margin-top: 15px;
display: flex;
flex-wrap: wrap;
gap: 15px;
}
.legend-item {
display: flex;
align-items: center;
font-size: 12px;
}
.legend-color {
width: 20px;
height: 20px;
margin-right: 5px;
border-radius: 3px;
}
.node-tooltip,
.edge-tooltip {
position: absolute;
background-color: rgba(0, 0, 0, 0.75);
color: #fff;
padding: 5px 10px;
border-radius: 4px;
font-size: 12px;
max-width: 250px;
z-index: 1000;
pointer-events: none;
}""")
# Start HTML content with simpler approach to loading Cytoscape
html_content = """<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Neo4j Schema Report</title>
<link rel="stylesheet" href="neo4j_schema_report.css">
<!-- Load only Cytoscape core -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.23.0/cytoscape.min.js"></script>
</head>
<body>
<h1>Neo4j Schema Report</h1>
<div class="toc">
<h2>Table of Contents</h2>
<ul>
<li><a href="#schema_diagram">Schema Diagram</a></li>
<li><a href="#overview">Overview</a></li>
<li><a href="#nodes">Node Labels</a></li>
<li><a href="#relationships">Relationship Types</a></li>
<li><a href="#constraints">Constraints</a></li>
<li><a href="#indexes">Indexes</a></li>
<li><a href="#node_details">Node Details</a></li>
<li><a href="#relationship_details">Relationship Details</a></li>
</ul>
</div>
<div class="section" id="schema_diagram">
<h2>Schema Diagram</h2>
<p>Interactive visualization of the database schema. Nodes represent node labels, and edges represent relationship types.</p>
<div class="controls">
<button id="layout-grid">Grid Layout</button>
<button id="layout-circle">Circular Layout</button>
<button id="layout-breadthfirst">Tree Layout</button>
<button id="layout-cose">Force Layout</button>
<button id="fit-graph">Fit View</button>
<button id="toggle-node-labels">Toggle Node Labels</button>
<button id="toggle-edge-labels">Toggle Edge Labels</button>
</div>
<div id="schema-visualization"></div>
<div class="legend">
<div class="legend-item">
<div class="legend-color" style="background-color: #8BC34A;"></div>
<span>Node Label</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background-color: #03A9F4;"></div>
<span>High Node Count</span>
</div>
</div>"""
# Add visualization script with node and edge data
node_elements = []
for label in schema_info["node_labels"]:
count = schema_info["node_counts"].get(label, 0)
properties = schema_info["node_properties"].get(label, [])
property_list = ", ".join(properties[:5]) + ("..." if len(properties) > 5 else "")
# Scale node size based on node count logarithmically (min 30, max 80)
node_size = 30
if count > 0:
node_size = min(80, max(30, 30 + 10 * (count.bit_length() - 1)))
# Color nodes based on their count
node_color = "#8BC34A" # Default color
if count > 1000:
node_color = "#03A9F4" # Higher count nodes
elif count > 100:
node_color = "#4CAF50" # Medium count nodes
# Escape single quotes in property list
safe_property_list = property_list.replace("'", "\\'")
node_elements.append("""{{
id: '{0}',
data: {{
id: '{0}',
label: '{0}',
count: {1},
properties: '{2}',
color: '{3}',
size: {4}
}}
}}""".format(
label,
count,
safe_property_list,
node_color,
node_size
))
edge_elements = []
for source, targets in schema_info["node_relationships"].items():
for target, rels in targets.items():
for rel in rels:
rel_type = rel["type"]
count = rel["count"]
edge_id = f"{source}_{rel_type}_{target}"
# Scale edge width based on relationship count logarithmically (min 1, max 8)
edge_width = 1
if count > 0:
edge_width = min(8, max(1, 1 + (count.bit_length() - 1)))
edge_elements.append("""{{
id: '{0}',
data: {{
id: '{0}',
source: '{1}',
target: '{2}',
label: '{3}',
count: {4},
width: {5}
}}
}}""".format(
edge_id,
source,
target,
rel_type,
count,
edge_width
))
# Add the visualization script - simplified and using only built-in layouts
html_content += """
<script>
document.addEventListener('DOMContentLoaded', function() {
// Create tooltip elements
const nodeTooltip = document.createElement('div');
nodeTooltip.className = 'node-tooltip';
nodeTooltip.style.display = 'none';
document.body.appendChild(nodeTooltip);
const edgeTooltip = document.createElement('div');
edgeTooltip.className = 'edge-tooltip';
edgeTooltip.style.display = 'none';
document.body.appendChild(edgeTooltip);
// Initialize Cytoscape with graph elements
const cy = cytoscape({
container: document.getElementById('schema-visualization'),
elements: [
// Nodes
""" + ",\n ".join(node_elements) + """,
// Edges
""" + ",\n ".join(edge_elements) + """
],
style: [
{
selector: 'node',
style: {
'label': 'data(label)',
'background-color': 'data(color)',
'width': 'data(size)',
'height': 'data(size)',
'text-valign': 'center',
'text-halign': 'center',
'font-size': '12px',
'color': '#fff',
'text-wrap': 'wrap',
'text-max-width': '80px',
'border-width': 2,
'border-color': '#333'
}
},
{
selector: 'edge',
style: {
'label': 'data(label)',
'width': function(ele) { return ele.data('width') + 'px'; },
'line-color': '#999',
'target-arrow-color': '#999',
'target-arrow-shape': 'triangle',
'curve-style': 'bezier',
'text-background-color': '#fff',
'text-background-opacity': 0.7,
'text-background-padding': '2px',
'font-size': '10px',
'color': '#333'
}
}
],
layout: {
name: 'grid',
padding: 50
}
});
// Add tooltips using basic DOM positioning
cy.on('mouseover', 'node', function(e) {
const node = e.target;
const pos = e.renderedPosition || e.position;
// Update tooltip content
nodeTooltip.innerHTML = '<strong>' + node.data('label') + '</strong><br/>' +
'Nodes: ' + node.data('count') + '<br/>' +
'Properties: ' + node.data('properties');
// Position tooltip near node
nodeTooltip.style.left = (pos.x + window.pageXOffset) + 'px';
nodeTooltip.style.top = (pos.y + window.pageYOffset - 70) + 'px';
nodeTooltip.style.display = 'block';
// Highlight the node
node.style({
'border-width': 4,
'border-color': '#ff0000'
});
});
cy.on('mouseout', 'node', function(e) {
nodeTooltip.style.display = 'none';
// Remove highlight
e.target.style({
'border-width': 2,
'border-color': '#333'
});
});
cy.on('mouseover', 'edge', function(e) {
const edge = e.target;
const midpoint = edge.midpoint();
// Update tooltip content
edgeTooltip.innerHTML = '<strong>' + edge.data('label') + '</strong><br/>' +
'Relationships: ' + edge.data('count');
// Position tooltip near edge midpoint
edgeTooltip.style.left = (midpoint.x + window.pageXOffset) + 'px';
edgeTooltip.style.top = (midpoint.y + window.pageYOffset - 40) + 'px';
edgeTooltip.style.display = 'block';
// Highlight the edge
edge.style({
'width': (parseInt(edge.data('width')) + 2) + 'px',
'line-color': '#ff0000',
'target-arrow-color': '#ff0000'
});
});
cy.on('mouseout', 'edge', function(e) {
edgeTooltip.style.display = 'none';
// Remove highlight
e.target.style({
'width': e.target.data('width') + 'px',
'line-color': '#999',
'target-arrow-color': '#999'
});
});
// Layout button handlers - using only built-in layouts
document.getElementById('layout-grid').addEventListener('click', function() {
cy.layout({
name: 'grid',
padding: 50,
animate: true,
animationDuration: 500
}).run();
});
document.getElementById('layout-circle').addEventListener('click', function() {
cy.layout({
name: 'circle',
padding: 50,
animate: true,
animationDuration: 500
}).run();
});
document.getElementById('layout-breadthfirst').addEventListener('click', function() {
cy.layout({
name: 'breadthfirst',
directed: true,
padding: 50,
animate: true,
animationDuration: 500
}).run();
});
document.getElementById('layout-cose').addEventListener('click', function() {
cy.layout({
name: 'cose',
padding: 50,
animate: true,
animationDuration: 500,
nodeOverlap: 20,
componentSpacing: 100
}).run();
});
document.getElementById('fit-graph').addEventListener('click', function() {
cy.fit();
});
let nodeLabelsVisible = true;
document.getElementById('toggle-node-labels').addEventListener('click', function() {
nodeLabelsVisible = !nodeLabelsVisible;
cy.style()
.selector('node')
.style({
'label': nodeLabelsVisible ? 'data(label)' : ''
})
.update();
});
let edgeLabelsVisible = true;
document.getElementById('toggle-edge-labels').addEventListener('click', function() {
edgeLabelsVisible = !edgeLabelsVisible;
cy.style()
.selector('edge')
.style({
'label': edgeLabelsVisible ? 'data(label)' : ''
})
.update();
});
// Initial layout and fit
setTimeout(function() {
// Start with a simple layout
cy.layout({
name: 'cose',
padding: 50,
componentSpacing: 100,
animate: false
}).run();
// Then fit the graph
setTimeout(function() {
cy.fit();
}, 200);
}, 300);
});
</script>
</div>
"""
# Overview section
html_content += """
<div class="section" id="overview">
<h2>Overview</h2>
<table>
<tr>
<th>Item</th>
<th>Count</th>
</tr>
<tr>
<td>Node Labels</td>
<td>{0}</td>
</tr>
<tr>
<td>Relationship Types</td>
<td>{1}</td>
</tr>
<tr>
<td>Property Keys</td>
<td>{2}</td>
</tr>
<tr>
<td>Constraints</td>
<td>{3}</td>
</tr>
<tr>
<td>Indexes</td>
<td>{4}</td>
</tr>
<tr>
<td>Total Nodes</td>
<td>{5}</td>
</tr>
<tr>
<td>Total Relationships</td>
<td>{6}</td>
</tr>
</table>
</div>
""".format(
len(schema_info["node_labels"]),
len(schema_info["relationship_types"]),
len(schema_info["property_keys"]),
len(schema_info["constraints"]),
len(schema_info["indexes"]),
sum(schema_info["node_counts"].values()),
sum(schema_info["relationship_counts"].values())
)
# Node Labels section
html_content += """
<div class="section" id="nodes">
<h2>Node Labels</h2>
<table>
<tr>
<th>Label</th>
<th>Count</th>
<th>Properties</th>
</tr>
"""
for label in sorted(schema_info["node_labels"]):
properties = schema_info["node_properties"].get(label, [])
count = schema_info["node_counts"].get(label, 0)
html_content += """
<tr>
<td>{0}</td>
<td>{1}</td>
<td>{2}</td>
</tr>
""".format(
label,
count,
", ".join(properties[:10]) + ("..." if len(properties) > 10 else "")
)
html_content += """
</table>
</div>
"""
# Relationship Types section
html_content += """
<div class="section" id="relationships">
<h2>Relationship Types</h2>
<table>
<tr>
<th>Type</th>
<th>Count</th>
<th>Properties</th>
</tr>
"""
for rel_type in sorted(schema_info["relationship_types"]):
properties = schema_info["relationship_properties"].get(rel_type, [])
count = schema_info["relationship_counts"].get(rel_type, 0)
html_content += """
<tr>
<td>{0}</td>
<td>{1}</td>
<td>{2}</td>
</tr>
""".format(
rel_type,
count,
", ".join(properties[:10]) + ("..." if len(properties) > 10 else "")
)
html_content += """
</table>
</div>
"""
# Constraints section
html_content += """
<div class="section" id="constraints">
<h2>Constraints</h2>
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th>For</th>
</tr>
"""
for constraint in schema_info["constraints"]:
name = constraint.get("name", "N/A")
constraint_type = constraint.get("type", constraint.get("description", "N/A"))
constraint_for = constraint.get("for", constraint.get("labelsOrTypes", "N/A"))
# Convert to string if it's a list or other non-string type
if not isinstance(constraint_for, str):
constraint_for = str(constraint_for)
html_content += """
<tr>
<td>{0}</td>
<td>{1}</td>
<td>{2}</td>
</tr>
""".format(name, constraint_type, constraint_for)
html_content += """
</table>
</div>
"""
# Indexes section
html_content += """
<div class="section" id="indexes">
<h2>Indexes</h2>
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th>For</th>
</tr>
"""
for index in schema_info["indexes"]:
name = index.get("name", "N/A")
index_type = index.get("type", index.get("description", "N/A"))
index_for = index.get("for", index.get("labelsOrTypes", "N/A"))
# Convert to string if it's a list or other non-string type
if not isinstance(index_for, str):
index_for = str(index_for)
html_content += """
<tr>
<td>{0}</td>
<td>{1}</td>
<td>{2}</td>
</tr>
""".format(name, index_type, index_for)
html_content += """
</table>
</div>
"""
# Node Details section
html_content += """
<div class="section" id="node_details">
<h2>Node Details</h2>
"""
for label in sorted(schema_info["node_labels"]):
properties = schema_info["node_properties"].get(label, [])
sample = schema_info["sample_nodes"].get(label, {})
count = schema_info["node_counts"].get(label, 0)
html_content += """
<div class="card">
<div class="card-header">
<span>{0}</span>
<span class="count-badge">{1} nodes</span>
</div>
<h4>Properties:</h4>
<table>
<tr>
<th>Property</th>
<th>Sample Value</th>
</tr>
""".format(label, count)
for prop in properties:
sample_value = sample.get(prop, "N/A")
if isinstance(sample_value, (dict, list)):
sample_value = str(sample_value)
# Trim long values
if isinstance(sample_value, str) and len(sample_value) > 100:
sample_value = sample_value[:100] + "..."
html_content += """
<tr>
<td>{0}</td>
<td>{1}</td>
</tr>
""".format(prop, sample_value)
html_content += """
</table>
<h4>Relationships:</h4>
<table>
<tr>
<th>Target Label</th>
<th>Relationship</th>
<th>Count</th>
</tr>
"""
# Get relationships where this label is the source
has_relationships = False
for target, rels in schema_info["node_relationships"].get(label, {}).items():
has_relationships = True
for rel in rels:
html_content += """
<tr>
<td>{0}</td>
<td>{1}</td>
<td>{2}</td>
</tr>
""".format(target, rel["type"], rel["count"])
if not has_relationships:
html_content += """
<tr>
<td colspan="3">No outgoing relationships</td>
</tr>
"""
html_content += """
</table>
</div>
"""
html_content += """
</div>
"""
# Relationship Details section
html_content += """
<div class="section" id="relationship_details">
<h2>Relationship Details</h2>
"""
for rel_type in sorted(schema_info["relationship_types"]):
properties = schema_info["relationship_properties"].get(rel_type, [])
sample = schema_info["sample_relationships"].get(rel_type, {})
count = schema_info["relationship_counts"].get(rel_type, 0)
html_content += """
<div class="card">
<div class="card-header">
<span>{0}</span>
<span class="count-badge">{1} relationships</span>
</div>
<h4>Properties:</h4>
<table>
<tr>
<th>Property</th>
<th>Sample Value</th>
</tr>
""".format(rel_type, count)
if properties:
for prop in properties:
sample_value = sample.get(prop, "N/A")
if isinstance(sample_value, (dict, list)):
sample_value = str(sample_value)
# Trim long values
if isinstance(sample_value, str) and len(sample_value) > 100:
sample_value = sample_value[:100] + "..."
html_content += """
<tr>
<td>{0}</td>
<td>{1}</td>
</tr>
""".format(prop, sample_value)
else:
html_content += """
<tr>
<td colspan="2">No properties</td>
</tr>
"""
html_content += """
</table>
<h4>Usage:</h4>
<table>
<tr>
<th>Source Label</th>
<th>Target Label</th>
</tr>
"""
# Find all node pairs that use this relationship
found_usage = False
for source, targets in schema_info["node_relationships"].items():
for target, rels in targets.items():
for rel in rels:
if rel["type"] == rel_type:
found_usage = True
html_content += """
<tr>
<td>{0}</td>
<td>{1}</td>
</tr>
""".format(source, target)
if not found_usage:
html_content += """
<tr>
<td colspan="2">No usage found</td>
</tr>
"""
html_content += """
</table>
</div>
"""
html_content += """
</div>
"""
# Close HTML
html_content += """
</body>
</html>
"""
# Write the HTML to file
with open(html_file, "w") as f:
f.write(html_content)
Parameters
| Name | Type | Default | Kind |
|---|---|---|---|
schema_info |
- | - | positional_or_keyword |
output_dir |
- | - | positional_or_keyword |
Parameter Details
schema_info: Parameter of type None
output_dir: Parameter of type None
Return Value
Returns unspecified type
Required Imports
import os
import json
import sys
from neo4j import GraphDatabase
from neo4j import time
Usage Example
# Example usage:
# result = generate_html_report(schema_info, output_dir)
Similar Components
AI-powered semantic similarity - components with related functionality:
-
function generate_neo4j_schema_report 64.3% similar
-
function generate_report 60.9% similar
-
function main_v59 53.3% similar
-
function generate_diagram_data 53.0% similar
-
function generate_html_from_msg 51.9% similar