db_annotate
  • Overview
  • Repository
  • Tickets
  • Statistics
  • Projects

Repository

Split the graph, add it to the html page

Parent commits : 1374813b9928c3f2c4818c9618252b09f658baea,
Children commits : bfbcd498a91d84ee535142fe170ec21e4f94a165,

By Laurent Defert on 2015-01-18 16:39:49
Split the graph, add it to the html page

Browse content
Difference with parent commit 1374813b9928c3f2c4818c9618252b09f658baea
Files modified:
dot.py
--- 
+++ 
@@ -8,7 +8,7 @@
     def render(self, gv_file):
         if self.exists():
             return
-        cmd = ['dot', '-Tpng', '-o', self.filename, gv_file]
+        cmd = ['dot', '-Tcmapx', '-o' + self.filename + '.map', '-Tpng', '-o' + self.filename, gv_file]
 
         try:
             output = subprocess.check_output(cmd)

gv.py
--- 
+++ 
@@ -6,101 +6,66 @@
     splines = true;
     node [shape=plaintext];
 
-    subgraph {
-    rank = sink;
-    label = "Legend table";
-    graph[style=solid];
-    bgcolor=gray;
-
-    // Legend
-    legend [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
-            <TR>
-                <TD BGCOLOR="white"><B>Legend (size without indexes/total size/lines count)</B></TD>
-                <TD >I'</TD>
-                <TD >NN'</TD>
-                <TD >U'</TD>
-            </TR>
-            <TR>
-                <TD ALIGN="left" BGCOLOR="gray">column</TD>
-                <TD BGCOLOR="gray"></TD>
-                <TD BGCOLOR="gray"></TD>
-                <TD BGCOLOR="gray"></TD>
-                <TD ALIGN="right" BGCOLOR="gray">data type</TD>
-            </TR>
-            <TR>
-                <TD ALIGN="left" BGCOLOR="limegreen">column_with_a_foreign_key</TD>
-                <TD BGCOLOR="limegreen"></TD>
-                <TD BGCOLOR="limegreen"></TD>
-                <TD BGCOLOR="limegreen"></TD>
-                <TD ALIGN="right" PORT="legend_foreign_key" BGCOLOR="limegreen">data type</TD>
-            </TR>
-            <TR>
-                <TD ALIGN="left" BGCOLOR="gray">I', NN', U': Index, Non null, Unique</TD>
-                <TD BGCOLOR="gray"></TD>
-                <TD BGCOLOR="gray"></TD>
-                <TD BGCOLOR="gray"></TD>
-                <TD ALIGN="right" BGCOLOR="gray"></TD>
-            </TR>
-        </TABLE>>];
-    legend2 [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
-            <TR>
-                <TD BGCOLOR="firebrick1"><B>Example table with errors</B></TD>
-                <TD BGCOLOR="firebrick1">I</TD>
-                <TD BGCOLOR="firebrick1">NN</TD>
-                <TD BGCOLOR="firebrick1">U</TD>
-                <TD BGCOLOR="firebrick1"></TD>
-                <TD BGCOLOR="firebrick1">table errors</TD>
-            </TR>
-            <TR>
-                <TD ALIGN="left" PORT="legend_primary_key" BGCOLOR="firebrick1">column_with_a_primary_key</TD>
-                <TD BGCOLOR="firebrick1"></TD>
-                <TD BGCOLOR="firebrick1"></TD>
-                <TD BGCOLOR="firebrick1"></TD>
-                <TD ALIGN="right" BGCOLOR="firebrick1">data type</TD>
-                <TD ALIGN="left" BGCOLOR="firebrick1">column errors</TD>
-            </TR>
-        </TABLE>>];
-
-        // Link the two table
-        // :e / :w to tell to link from the East border of the cell to the West border
-        // of the other cell
-        legend:legend_foreign_key:e -> legend2:legend_primary_key:w;
-        image -> legend [style=invis];
-        }
 """
 GV_FOOTER = """
 }
 """
 GV_SEPARATOR = '__42deadbeef__'
 
+
 class GV(OutputFile):
-    def __init__(self, filename):
+    def __init__(self, filename, minimap=False):
         super(GV, self).__init__(filename)
-        self.tables = {}
+        self.tables = []
+        self.minimap = minimap
 
     def add_header(self):
         self.write(GV_HEADER)
 
-    def add_table(self, name, errors, sizes, keys, indexes, columns):
+    def add_table(self, name, errors, sizes, keys, indexes, columns, highlight=False):
+        if name in self.tables:
+            return
+        self.tables.append(name)
         sizes = sizes[:2] + [sizes[4]]
-        # Display the table in red when there is no entries
+
         color = 'white'
         if len(errors):
             color = 'firebrick1'
 
-        table = """{name} [label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
+        if self.minimap:
+            fontsize = 'fontsize="7"'
+        elif highlight:
+            fontsize = 'fontsize="16"'
+        else:
+            fontsize = 'fontsize="10"'
+
+        url = ''
+        if not highlight:
+            url = 'URL="%s.html"' % name
+
+        table = """{name} [ {url} {fontsize} label=<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
         <TR>
             <TD PORT="{name}" BGCOLOR="{color}"><B>{name} ({sizes})</B></TD>
-            <TD BGCOLOR="{color}">I</TD>
-            <TD BGCOLOR="{color}">NN</TD>
-            <TD BGCOLOR="{color}">U</TD>
-            <TD BGCOLOR="{color}"></TD>
-            <TD ALIGN="left" BGCOLOR="{color}">{errors}</TD>
-        </TR>""".format(name=name,
+        """
+        if not self.minimap:
+            table += """
+                <TD BGCOLOR="{color}">I</TD>
+                <TD BGCOLOR="{color}">NN</TD>
+                <TD BGCOLOR="{color}">U</TD>
+                <TD BGCOLOR="{color}"></TD>
+                <TD ALIGN="left" BGCOLOR="{color}">{errors}</TD>"""
+        table += '</TR>'
+        table = table.format(name=name,
                         color=color,
                         sizes='/'.join([str(n) for n in sizes]),
-                        errors=', '.join(errors))
+                        errors=', '.join(errors),
+                        fontsize=fontsize,
+                        url=url)
         self.write(table)
+
+        if self.minimap:
+            self.write("</TABLE>>];")
+            return
 
         for col in columns:
             col_id = "%s%s%s" % (name, GV_SEPARATOR, col.name)
@@ -139,24 +104,36 @@
         self.write("</TABLE>>];")
 
     def add_constraint(self, src, dst):
-        self.write("%s:%s1:e -> %s:%s:w;" % (src[0], GV_SEPARATOR.join(src),
-                                    dst[0], GV_SEPARATOR.join(dst)))
+        if self.minimap:
+            self.write("%s -> %s;" % (src[0], dst[0]))
+        else:
+            self.write("%s:%s1:e -> %s:%s:w;" % (src[0], GV_SEPARATOR.join(src),
+                                                 dst[0], GV_SEPARATOR.join(dst)))
 
     def add_missing_constraint(self, table, column, other_table, error):
-        self.write('%s:%s1:e -> %s [label="%s" fontcolor="red", color="red"];' % (table, GV_SEPARATOR.join([table, column]),
+        if self.minimap:
+            self.write('%s -> %s [fontcolor="red", color="red"];' % (table, other_table))
+        else:
+            self.write('%s:%s1:e -> %s [label="%s" fontcolor="red", color="red"];' % (table, GV_SEPARATOR.join([table, column]),
                                     other_table, error))
 
     def add_inherited(self, src, dst):
         # :s for the South side of the table
-        self.write('%s -> %s:%s [label="inherits" fontcolor=limegreen color=limegreen];' % (dst, src, src))
+        if self.minimap:
+            self.write('%s -> %s [fontcolor="limegreen" color="limegreen"];' % (dst, src))
+        else:
+            self.write('%s -> %s:n [label="inherits" fontcolor="limegreen" color="limegreen"];' % (dst, src))
 
     def add_duplicate(self, src, dst, duplicate_type):
-        self.write('%s -> %s:%s [label="%s" fontcolor=red color=red];' % (src, dst, dst, ', '.join(duplicate_type)))
+        if self.minimap:
+            self.write('%s -> %s [fontcolor="red" color="red"];' % (src, dst))
+        else:
+            self.write('%s -> %s:n [label="%s" fontcolor="red" color="red"];' % (src, dst, ', '.join(duplicate_type)))
 
     def add_footer(self):
         self.write(GV_FOOTER)
 
     def add_namespace(self, namespace, tables):
-        self.write('%s [label="%s", shape=square, fontsize=64];' % (namespace, namespace.title()))
+        self.write('%s [label="%s", shape="square"];' % (namespace, namespace.title()))
         for table in tables:
-            self.write('%s -> %s:%s [style="dashed"];' % (namespace, table, table))
+            self.write('%s -> %s [style="dashed"];' % (namespace, table))

html.py
--- 
+++ 
@@ -1,7 +1,7 @@
 import os
 
 from db_size import humanize
-from output_file import OutputFile
+from output_file import OutputFile, OUTPUT_DIR
 
 HTML_BODY = """
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
@@ -32,7 +32,25 @@
         self.write(HTML_FOOTER)
 
 
-class HtmlFile(OutputFile):
+class TableFile(OutputFile):
+    @staticmethod
+    def get_table_html(img):
+        map_file = img + '.map'
+        img = os.path.basename(img)
+        html = '<img src="%s" usemap="#mainmap" /><map id="mainmap" name="mainmap">' % img
+        map_file = open(map_file, 'r')
+        html += map_file.read()
+        map_file.close()
+        html += '</map>'
+        return html
+
+    def render(self):
+        self.write(HTML_BODY % self.filename)
+        self.write('<a href="index.html">Back...</a><br/>')
+        self.write(TableFile.get_table_html(self.filename.replace('.html', '.png')))
+        self.write(HTML_FOOTER)
+
+class IndexFile(OutputFile):
     def render(self, objects, db_size):
         for obj in objects:
             for _obj in obj:
@@ -66,9 +84,11 @@
 
         for table in tables:
             self.write('<tr>')
-            for _table in table:
+            for no, _table in enumerate(table):
+                if no == 0:
+                    _table = '<a href="%s.html" >%s</a>' % (_table, _table)
                 self.write('<td>%s</td>' % _table)
             self.write('</tr>')
         self.write('</table>')
-
+        self.write(TableFile.get_table_html(os.path.join(OUTPUT_DIR, 'map.png')))
         self.write(HTML_FOOTER)

main.py
--- 
+++ 
@@ -8,7 +8,7 @@
 from db_size import DBSize
 from dot import DotFile
 from gv import GV
-from html import HtmlFile
+from html import IndexFile, TableFile
 from output_file import OutputFile
 
 
@@ -27,55 +27,85 @@
 
     OutputFile.create_outputdir()
 
-    gv = GV('relationship.gv')
+    gv_map = GV('map.gv', minimap=True)
+    gv_tables = {}
+    tables = {}
+
     db_size = DBSize()
 
     # DB size pies
     for table in db.get_tables():
         sizes = db.get_table_size(table)
         db_size.add_table(table, sizes)
+        gv_tables[table] = GV(table + '.gv')
+        gv_tables[table].add_header()
+
     imgs = db_size.render()
 
     # Tables
-    if not gv.exists():
-        gv.add_header()
+    if not gv_map.exists():
+        gv_map.add_header()
+
         for table in db.get_tables():
             columns = db.get_columns(table)
             sizes = db.get_table_size(table)
             keys = db.get_table_keys(table)
             indexes = db.get_table_index(table)
             errors = db.get_table_errors(table)
-            gv.add_table(table, errors, sizes, keys, indexes, columns)
+            gv_map.add_table(table, errors, sizes, keys, indexes, columns)
+            gv_tables[table].add_table(table, errors, sizes, keys, indexes, columns, True)
+            tables[table] = (table, errors, sizes, keys, indexes, columns)
 
         # Constraints
         for table in db.get_tables():
             for src, dst in db.get_foreign_keys(table):
-                gv.add_constraint(src, dst)
+                gv_tables[table].add_table(*tables[dst[0]])
+                gv_tables[table].add_constraint(src, dst)
+                gv_tables[dst[0]].add_table(*tables[table])
+                gv_tables[dst[0]].add_constraint(src, dst)
+                gv_map.add_constraint(src, dst)
+
         # Missing constraints
         for table in db.get_tables():
             for constraint in db.get_missing_constraints(table):
-                gv.add_missing_constraint(*constraint)
+                gv_tables[table].add_table(*tables[constraint[2]])
+                gv_tables[table].add_missing_constraint(*constraint)
+                gv_tables[constraint[2]].add_table(*tables[table])
+                gv_tables[constraint[2]].add_missing_constraint(*constraint)
+                gv_map.add_missing_constraint(*constraint)
 
         # Inheritance link
         for src, dst in db.get_inherited_tables():
-            gv.add_inherited(src, dst)
+            gv_map.add_inherited(src, dst)
+            gv_tables[src].add_table(*tables[dst])
+            gv_tables[src].add_inherited(src, dst)
+            gv_tables[dst].add_table(*tables[src])
+            gv_tables[dst].add_inherited(src, dst)
 
-        for tables, duplicate_type in db.get_duplicated_tables().items():
-            src, dst = tables.split('/')
-            gv.add_duplicate(src, dst, duplicate_type)
+        for _tables, duplicate_type in db.get_duplicated_tables().items():
+            src, dst = _tables.split('/')
+            gv_map.add_duplicate(src, dst, duplicate_type)
+            gv_tables[src].add_table(*tables[dst])
+            gv_tables[src].add_duplicate(src, dst, duplicate_type)
+            gv_tables[dst].add_table(*tables[src])
+            gv_tables[dst].add_duplicate(src, dst, duplicate_type)
 
         namespaces = db.get_namespaces().items()
         if len(namespaces) != 1:
             # Show namespaces only when there are more than one
             for namespace, tables in namespaces:
-                gv.add_namespace(namespace, tables)
+                gv_map.add_namespace(namespace, tables)
 
-        gv.add_footer()
+        for gv in [gv_map] + list(gv_tables.values()):
+            gv.add_footer()
+            gv.close()
 
-    gv.close()
-    dot = DotFile('relationship.png')
-    dot.render(gv.filename)
+            dot = DotFile(gv.basename.replace('.gv', '.png'))
+            dot.render(gv.filename)
+
+            table_html = TableFile(gv.basename.replace('.gv', '.html'))
+            table_html.render()
 
     # Generate HTML files
-    html_file = HtmlFile('index.html')
+    html_file = IndexFile('index.html')
     html_file.render(imgs, db_size)

output_file.py
--- 
+++ 
@@ -6,6 +6,7 @@
 class OutputFile:
     def __init__(self, filename):
         self.fd = None
+        self.basename = filename
         self.filename = os.path.join(OUTPUT_DIR, filename)
 
     @staticmethod

Generated with KisssPM