🔍 Code Extractor

function generate_html_report

Maturity: 25

Generate HTML report from schema info

File:
/tf/active/vicechatdev/neo4j_schema_report.py
Lines:
277 - 1059
Complexity:
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

    Generates a comprehensive schema report of a Neo4j graph database, including node labels, relationships, properties, constraints, indexes, and sample data, outputting multiple file formats (JSON, HTML, Python snippets, Cypher examples).

    From: /tf/active/vicechatdev/neo4j_schema_report.py
  • function generate_report 60.9% similar

    Generates a text-based statistical analysis report from session data and saves it to the configured reports folder.

    From: /tf/active/vicechatdev/full_smartstat/app.py
  • function main_v59 53.3% similar

    Demonstrates a SmartStat SQL workflow by loading a database schema, initializing a SQL query generator, and generating SQL queries from natural language requests with detailed output and metadata.

    From: /tf/active/vicechatdev/full_smartstat/demo_sql_workflow.py
  • function generate_diagram_data 53.0% similar

    Transforms Neo4j schema information into a structured format suitable for graph visualization, creating separate node and edge data structures.

    From: /tf/active/vicechatdev/neo4j_schema_report.py
  • function generate_html_from_msg 51.9% similar

    Converts an email message object into a formatted HTML representation with styling, headers, body content, and attachment information.

    From: /tf/active/vicechatdev/msg_to_eml.py
← Back to Browse