Interactive Dependency Report (SVG)

This report shows a user a map of all the dependencies related to the specified buildlife. The user can click on particular builds to highlight connections it has to other builds in the tree. It also highlights any dependencies that may be in conflict in the tree. It is designed to run as a buildlife integration report and must be designated as such to work correctly. SVG is not natively supported by Internet Explorer. Please see the Interactive Dependency Report (VML) for use with Internet Explorer.


Meta-Data Script:

There is no Meta-Data Script associated with this report.


Context Script:

import com.urbancode.commons.dag.*;
import java.util.*;
import com.google.gson.Gson;

int DEP_VIS_PROJECT_WIDTH = 250;
int DEP_VIS_PROJECT_HEIGHT = 100;
int DEP_VIS_MARGIN = 20;
int DEP_VIS_BUILD_LIFE_WIDTH = 200;
int DEP_VIS_BUILD_LIFE_HEIGHT = 22;

public TableDisplayableGraph getDependencyGraph(buildLife) {
    TableDisplayableGraph graph = new TableDisplayableGraph(true);

    graph.createVertex(buildLife);

    addDependenciesToGraph(buildLife, graph);

    return graph;
}

public void addDependenciesToGraph(buildLife, graph) {
    for (Object dep : buildLife.getDependencyBuildLifeArray()) {
        if (!graph.contains(dep)) {
            graph.createVertex(dep);
            addDependenciesToGraph(dep, graph);
        }
        graph.addArc(buildLife, dep);
    }
}

public Map convertGraphToProjectMap(graph) {
    HashMap map = new HashMap();
    Vertex[] sources = graph.getSourceVertexArray();
    for (Vertex vertex : sources) {
        addVertexToMap(vertex, map);
    }
    return map;
}

public void addVertexToMap(vertex, map) {
    Object buildLife = vertex.getData();
    Object project = buildLife.getCodestationProject();
    List buildLifeList = map.get(project);
    if (buildLifeList == null) {
        buildLifeList = new ArrayList();
        map.put(project, buildLifeList);
    }
    if (!buildLifeList.contains(buildLife)) {
        buildLifeList.add(buildLife);
    }

    for (child : vertex.getOutgoingArcsVertexArray()) {
        addVertexToMap(child, map);
    }
}

public void convertVertexToLine(lineList, vertex, incXMap, incYMap, outXMap, outYMap) {
    Object buildLife = vertex.getData();
    for (Object depVertex : vertex.getOutgoingArcsVertexArray()) {
        Object depBuildLife = depVertex.getData();
        String startX = outXMap.get(buildLife);
        String startY = outYMap.get(buildLife);
        String endX = incXMap.get(depBuildLife);
        String endY = incYMap.get(depBuildLife);
        HashMap lineMap = new HashMap();
        lineMap.put("x1", startX);
        lineMap.put("y1", startY);
        lineMap.put("x2", endX);
        lineMap.put("y2", endY);
        lineMap.put("stroke", "black");
        lineMap.put("stroke-width", "1px");
        lineMap.put("id", "build"+depBuildLife.getId()+"_build"+buildLife.getId());
        lineList.add(lineMap);
        convertVertexToLine(lineList, depVertex, incXMap, incYMap, outXMap, outYMap);
    }
}

/**
 * Prune all graph elements which are from projects which are NOT ancestors of projects with conflicts
 * Can be used to display a leaner graph focusing on dependency conflicts
 */
/*
public void pruneGraph(graph, depMap) {
    // construct a list of all build lives causing conflicts
    List conflictList = new ArrayList();
    for (depBuildLifeList : depMap.values()) {
        if (depBuildLifeList.size() > 1) {
            conflictList.addAll(depBuildLifeList);
        }
    }
    Set keepVertexSet = new HashSet();
    for (depBuildLife : conflictList) {
        System.out.println("Found a conflict for build life " + depBuildLife.getId() + " of " + depBuildLife.getCodestationProject().getName());
        Set parentVertices = getAllAncestorVertices(graph.getVertex(depBuildLife));
        keepVertexSet.addAll(parentVertices);
        keepVertexSet.add(graph.getVertex(depBuildLife));
    }
    for (Vertex vertex : graph.getVertexArray()) {
        if (!keepVertexSet.contains(vertex)) {
            graph.removeVertex(vertex, false);
            depMap.remove(vertex.getData().getCodestationProject());
        }
    }
}
*/

/**
 * Collect all the ancestor vertices of the given vertex (not including the given vertex).
 */
public Set getAllAncestorVertices(vertex) {
    Set parentSet = new HashSet();
    for (Vertex parentVertex : vertex.getIncomingArcsVertexArray()) {
        getAllAncestorVertices(parentVertex, parentSet);
    }
    return parentSet;
}

/**
 * Add the given vertex to the given set, then recurse for all parents of the given vertex.
 */
public void getAllAncestorVertices(vertex, parentSet) {
    parentSet.add(vertex);
    for (Vertex parentVertex : vertex.getIncomingArcsVertexArray()) {
        getAllAncestorVertices(parentVertex, parentSet);
    }
}
    
TableDisplayableGraph graph = getDependencyGraph(buildLife);

Map depMap = convertGraphToProjectMap(graph);

// Uncomment this line and the pruneGraph method to display a leaner graph focusing on dependency conflicts
//pruneGraph(graph, depMap);

graph.calculateVertexSpacing();

// order the map by max depth
Map depthMap = new HashMap();
for (depProject : depMap.keySet()) {
    int maxDepth = 0;
    for (depBuildLife : depMap.get(depProject)) {
        int depth = graph.getVertex(depBuildLife).getDepth() - 1;
        if (depth > maxDepth) { maxDepth = depth; }
    }
    Integer depth = new Integer(maxDepth);
    List projectList = depthMap.get(depth);
    if (projectList == null) {
        projectList = new ArrayList();
        depthMap.put(depth, projectList);
    }
    projectList.add(depProject);
}

List depthList = new ArrayList();
depthList.addAll(depthMap.keySet());
Collections.sort(depthList);

Map incXMap = new HashMap();
Map incYMap = new HashMap();
Map outXMap = new HashMap();
Map outYMap = new HashMap();

List rectList = new ArrayList();
List textList = new ArrayList();
List lineList = new ArrayList();


int maxWidth = 1;
for (Integer depth : depthList) {
    List projectList = depthMap.get(depth);
    if (projectList.size() > maxWidth) {
        maxWidth = projectList.size();
    }
}

class ProjectComparator implements Comparator {
    public int compare(Object obj1, Object obj2) {
        int weight1 = 0;
        for (Object buildLife : depMap.get(obj1)) {
            Vertex vertex = graph.getVertex(buildLife);
            weight1 += vertex.getIncomingArcCount() + vertex.getOutgoingArcCount();
        }
        int weight2 = 0;
        for (Object buildLife : depMap.get(obj2)) {
            Vertex vertex = graph.getVertex(buildLife);
            weight2 += vertex.getIncomingArcCount() + vertex.getOutgoingArcCount();
        }
        if (weight1 < weight2) {
            return 1;
        }
        else if (weight1 > weight2) {
            return -1;
        }
        else {
            return 0;
        }
    }
}

int width = 0;
int height = 0;
for (Integer depth : depthList) {
    List pList = depthMap.get(depth);
    double xOffset = (maxWidth + pList.size() + (depth == 0 ? 1 : 0)) * 0.5 - pList.size();
    int xAdjust = ((depth * (depth-1)) / 2 * (DEP_VIS_PROJECT_WIDTH / 50)) + 5; 
    xAdjust = xAdjust % ( (DEP_VIS_PROJECT_WIDTH + DEP_VIS_MARGIN) / 2); // recenter when drift is past half-way point of center rectangle
    Collections.sort(pList, new ProjectComparator());
    LinkedList projectList = new LinkedList();
    for (int p=0; p<pList.size(); p++) {
        Object project = pList.get(p);
        if (p % 2 == 0) {
            projectList.addFirst(project);
        } else {
            projectList.addLast(project);
        }
    }
    
    for (int p=0; p<projectList.size(); p++) {
        Object project = projectList.get(p);
        List depBuildLifeList = depMap.get(project);

        Map rect = new HashMap();
        int pX = (xOffset + p) * (DEP_VIS_PROJECT_WIDTH + DEP_VIS_MARGIN) + xAdjust;
        int pY = depth.intValue() * (DEP_VIS_PROJECT_HEIGHT + DEP_VIS_MARGIN);
        width = Math.max(width, pX + DEP_VIS_PROJECT_WIDTH);
        height = Math.max(height, pY + DEP_VIS_PROJECT_HEIGHT);

        rect.put("x", String.valueOf(pX));
        rect.put("y", String.valueOf(pY));
        rect.put("width", String.valueOf(DEP_VIS_PROJECT_WIDTH));
        rect.put("height", String.valueOf(DEP_VIS_PROJECT_HEIGHT));
        rect.put("rx", String.valueOf(10));
        rect.put("className", "project");
        if (depBuildLifeList.size() > 1) {
            rect.put("className", "project conflict");
        }
        rect.put("id", "project"+project.getId());
        rectList.add(rect);
        
        Map text = new HashMap();
        text.put("x", String.valueOf(pX + 15));
        text.put("y", String.valueOf(pY + 15));
        text.put("text", project.getName());
        text.put("id", "project"+project.getId()+"-text");
        textList.add(text);
        
        for (int b=0; b<depBuildLifeList.size(); b++) {
            Object depBuildLife = depBuildLifeList.get(b);
            
            Map rect = new HashMap();
            int blX = pX + 20;
            int blY = pY + 30 + (30*b);
            rect.put("x", String.valueOf(blX));
            rect.put("y", String.valueOf(blY));
            rect.put("width", String.valueOf(DEP_VIS_BUILD_LIFE_WIDTH));
            rect.put("height", String.valueOf(DEP_VIS_BUILD_LIFE_HEIGHT));
            rect.put("rx", String.valueOf(5));
            rect.put("className", "build");
            rect.put("id", "build"+depBuildLife.getId());
            rectList.add(rect);
            
            incXMap.put(depBuildLife, String.valueOf(blX + (DEP_VIS_BUILD_LIFE_WIDTH / 2)));
            incYMap.put(depBuildLife, String.valueOf(blY));
            
            outXMap.put(depBuildLife, String.valueOf(blX + (DEP_VIS_BUILD_LIFE_WIDTH / 2)));
            outYMap.put(depBuildLife, String.valueOf(blY + DEP_VIS_BUILD_LIFE_HEIGHT));

            Map text = new HashMap();
            text.put("x", String.valueOf(blX + 10));
            text.put("y", String.valueOf(blY + 13));
            text.put("id", "build"+depBuildLife.getId()+"-text");
            StringBuilder sb = new StringBuilder();
            sb.append("ID: ").append(depBuildLife.getId());
            if (depBuildLife.getLatestStampValue() != null) {
                sb.append(" Stamp: ").append(depBuildLife.getLatestStampValue());
            }
            text.put("text", sb.toString());
            textList.add(text);
        }
    }
}

for (Vertex vertex : graph.getSourceVertexArray()) {
    convertVertexToLine(lineList, vertex, incXMap, incYMap, outXMap, outYMap);
}

context.put("width", String.valueOf(width + 50));
context.put("height", String.valueOf(height + 10));
context.put("rectList", rectList);
context.put("textList", textList);
context.put("lineList", lineList);

//
// Create Highlight Data JSON string
//

Map parentElementIds = new HashMap();
Map childElementIds  = new HashMap();

public void addHighlightRef(elementIds, source, target) {
    Set targetList = elementIds.get(source);
    if (targetList == null) {
        targetList = new HashSet();
        elementIds.put(source, targetList);
    }
    targetList.add(target);
}

for (List buildLifeList : depMap.values()) {
    for (Object buildLife : buildLifeList) {
        Object project = buildLife.getCodestationProject();
        for (Object childBuildLife : buildLife.getDependencyBuildLifeArray()) {
            Object depProject = childBuildLife.getCodestationProject();

            String parentProjectId    = "project"+project.getId();
            String parentBuildId      = "build"+buildLife.getId();
            String child2ParentLineId = "build"+childBuildLife.getId()+"_build"+buildLife.getId();
            String childProjectId     = "project"+depProject.getId();
            String childBuildId       = "build"+childBuildLife.getId();

            // clicking on the project
            addHighlightRef(parentElementIds, childProjectId, parentProjectId);
            addHighlightRef(parentElementIds, childProjectId, child2ParentLineId);
            // clicking on the build-life
            addHighlightRef(parentElementIds, childBuildId, parentBuildId);
            addHighlightRef(parentElementIds, childBuildId, child2ParentLineId);

            addHighlightRef(childElementIds, parentProjectId, childProjectId);
            addHighlightRef(childElementIds, parentProjectId, child2ParentLineId);
            addHighlightRef(childElementIds, parentBuildId, childBuildId);
            addHighlightRef(childElementIds, parentBuildId, child2ParentLineId);
        }
    }
}

context.put("parentElementIds", new Gson().toJson(parentElementIds));
context.put("childElementIds", new Gson().toJson(childElementIds));

return context;

Template Text:

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svg="http://www.w3.org/2000/svg"
    height="$height" width="$width" style="overflow: auto">

  <defs>
    <linearGradient id="orange_red" x1="0%" y1="0%" x2="100%" y2="0%">
      <stop offset="0%" style="stop-color:rgb(255,255,0); stop-opacity:1"/>
      <stop offset="100%" style="stop-color:rgb(255,0,0); stop-opacity:1"/>
    </linearGradient>
  </defs>

  <!-- Adapted from prototype.js -->
  <script type="text/javascript">
      /* <![CDATA[ */

      function $(element) {
          if (arguments.length > 1) {
              for (var i = 0, elements = [], length = arguments.length; i < length; i++)
                  elements.push($(arguments[i]));
              return elements;
          }
          if (typeof element === 'string')
              element = document.getElementById(element);
         return element;
      }

      var Element = {
        hasClassName: function(element, className) {
              if (!(element = $(element))) return false;
              var elementClassName = (element.className && element.className.baseVal) || '';
              return (elementClassName.length > 0 && (elementClassName == className || new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
        },

        addClassName: function(element, className) {
            if (!(element = $(element))) return;
            if (!Element.hasClassName(element, className))
                element.className.baseVal += (element.className ? ' ' : '') + className;
            return element;
        },

        removeClassName: function(element, className) {
            if (!(element = $(element)) || !Element.hasClassName(element, className)) return;
            var elementClassName = (element.className && element.className.baseVal) || '';
            element.className.baseVal = elementClassName.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ');
            return element;
        },

        toggleClassName: function(element, className) {
            if (!(element = $(element))) return;
            return Element[Element.hasClassName(element, className) ? 'removeClassName' : 'addClassName'](element, className);
        },

        visible: function(element) {
            return $(element).style.display != 'none';
        },

        toggle: function(element) {
            element = $(element);
            Element[Element.visible(element) ? 'hide' : 'show'](element);
            return element;
        },

        hide: function(element) {
            element = $(element);
            element.style.display = 'none';
            return element;
        },

        show: function(element) {
            element = $(element);
            element.style.display = '';
            return element;
        }
      }
      /* ]]> */
  </script>

  <!-- custom styles and scripts for this page -->
  <style type="text/css">
      svg           { font-size: 10px; font-family: Courier}
      line          { stroke: black; stroke-width: 1px; opacity: 0.5}
      text          { fill: black; }
      rect.project  { fill: blue;  fill-opacity: 0.7; stroke: black; stroke-width: 2px;}
      rect.build    { fill: white; fill-opacity: 0.7; stroke: black; stroke-width: 2px;}
      rect.conflict { fill: red; }

      line.highlight-parent, line.highlight-child { stroke-width: 2px; opacity: 1; stroke: purple }
      rect.highlight-parent, rect.highlight-child { fill: #C0FF3E; }
      rect.highlight-main   { fill: yellow; }
      /*rect.highlight-child  { fill: #FF9955; fill-opacity: 1; }*/
  </style>
  <script type="text/javascript">
   /* <![CDATA[ */
    var parentElementIds = $parentElementIds ;
    var childElementIds = $childElementIds ;

    function highlight(elem) {
        elem = $(elem);
        if (elem.tagName == 'text' && elem.id.indexOf('-text') > -1) {
            elem = $(elem.id.substring(0, elem.id.length - '-text'.length));
        }

        var clearOnly = Element.hasClassName(elem, 'highlight-main');
        resetAllHighlighting();
        if (clearOnly) return;

        Element.addClassName(elem, 'highlight-main');

        var parentIds = parentElementIds[elem.id];
        for (i in parentIds) {
            Element.addClassName(parentIds[i], 'highlight-parent');
        }

        var childIds = childElementIds[elem.id];
        for (i in childIds) {
            Element.addClassName(childIds[i], 'highlight-child');
        }
    }

    function resetAllHighlighting() {
        var rects = document.getElementsByTagName('rect');
        for (i in rects) {
            Element.removeClassName(rects[i], 'highlight-parent')
            Element.removeClassName(rects[i], 'highlight-main')
            Element.removeClassName(rects[i], 'highlight-child')
        }
        var lines = document.getElementsByTagName('line');
        for (i in lines) {
            Element.removeClassName(lines[i], 'highlight-child');
            Element.removeClassName(lines[i], 'highlight-parent');
        }
    }

    /* ]]> */
  </script>
  <!-- content -->
 
  <g font-family="Courier" font-size="10" >

    <text x="10" y="10" width="200" height="300">
        <tspan x="10" dy="1.1em">Click any project or build to select it and highlight the items which depend upon that.</tspan>
        <tspan x="10" dy="1.1em">Click a second time to unselect the item.</tspan>
    </text>

        
#foreach( $line in $lineList )
    #set ($xA = $line.get("x1"))
    #set ($yA = $line.get("y1"))
    #set ($xB = $line.get("x2"))
    #set ($yB = $line.get("y2"))
    #set ($id = $line.get("id"))
<line id="$id" x1="$xA" y1="$yA" x2="$xB" y2="$yB"/>
#end
  
#foreach( $rect in $rectList )
    #set ($id = $rect.get("id"))
    #set ($className = $rect.get("className"))
    #set ($x = $rect.get("x"))
    #set ($y = $rect.get("y"))
    #set ($width = $rect.get("width"))
    #set ($height = $rect.get("height"))
    #set ($rx = $rect.get("rx"))
<rect id="$id" class="$className" x="$x" y="$y" width="$width" height="$height" rx="$rx" onclick="highlight(this)"/>
#end
        
#foreach( $text in $textList )
    #set ($x = $text.get("x"))
    #set ($y = $text.get("y"))
    #set ($content = $text.get("text"))
    #set ($id = $text.get("id"))
<text id="$id" x="$x" y="$y" onclick="highlight(this)">$content</text>
#end
  
  </g>
  
</svg>

Related Content

AnthillPro Velocity Reports
Interactive Dependency Report (VML)